Skip to content

Commit 1765875

Browse files
authored
Merge branch 'main' into fix/perps/tpsl-optimistic-update
2 parents 1a41d46 + c217cdd commit 1765875

48 files changed

Lines changed: 1876 additions & 317 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/CODEOWNERS

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,12 @@ certs/certificate.pem @MetaMask/mobile-adm
5050
metro.transform.js @MetaMask/mobile-platform @MetaMask/core-platform
5151

5252
# Ramps Team
53-
app/components/UI/Ramp/ @MetaMask/ramp
54-
app/reducers/fiatOrders/ @MetaMask/ramp
53+
app/components/UI/Ramp/ @MetaMask/ramp
54+
app/reducers/fiatOrders/ @MetaMask/ramp
55+
app/core/Engine/controllers/ramps-controller @MetaMask/ramp
56+
app/core/Engine/messengers/ramps-controller-messenger @MetaMask/ramp
57+
app/core/Engine/messengers/ramps-service-messenger @MetaMask/ramp
58+
app/selectors/rampsController @MetaMask/ramp
5559

5660
# Card Team
5761
app/components/UI/Card/ @MetaMask/card

app/component-library/components-temp/HeaderCenter/HeaderCenter.test.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,42 @@ describe('HeaderCenter', () => {
150150

151151
expect(queryByTestId(START_ACCESSORY_TEST_ID)).not.toBeOnTheScreen();
152152
});
153+
154+
it('renders startButtonIconProps when provided', () => {
155+
const onPress = jest.fn();
156+
const { getByTestId } = render(
157+
<HeaderCenter
158+
title="Title"
159+
startButtonIconProps={{
160+
iconName: IconName.Menu,
161+
onPress,
162+
testID: 'custom-start-button',
163+
}}
164+
/>,
165+
);
166+
167+
expect(getByTestId('custom-start-button')).toBeOnTheScreen();
168+
});
169+
170+
it('startButtonIconProps takes priority over onBack', () => {
171+
const onBack = jest.fn();
172+
const onPress = jest.fn();
173+
const { getByTestId, queryByTestId } = render(
174+
<HeaderCenter
175+
title="Title"
176+
onBack={onBack}
177+
backButtonProps={{ testID: BACK_BUTTON_TEST_ID }}
178+
startButtonIconProps={{
179+
iconName: IconName.Menu,
180+
onPress,
181+
testID: 'custom-start-button',
182+
}}
183+
/>,
184+
);
185+
186+
expect(getByTestId('custom-start-button')).toBeOnTheScreen();
187+
expect(queryByTestId(BACK_BUTTON_TEST_ID)).not.toBeOnTheScreen();
188+
});
153189
});
154190

155191
describe('close button', () => {

app/component-library/components-temp/HeaderCenter/HeaderCenter.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,16 @@ const HeaderCenter: React.FC<HeaderCenterProps> = ({
4343
onClose,
4444
closeButtonProps,
4545
endButtonIconProps,
46+
startButtonIconProps,
4647
twClassName,
4748
testID,
4849
...headerBaseProps
4950
}) => {
5051
// Build the startButtonIconProps with back button if needed
5152
const resolvedStartButtonIconProps = useMemo(() => {
53+
if (startButtonIconProps) {
54+
return startButtonIconProps;
55+
}
5256
if (onBack || backButtonProps) {
5357
return {
5458
iconName: IconName.ArrowLeft,
@@ -57,7 +61,7 @@ const HeaderCenter: React.FC<HeaderCenterProps> = ({
5761
} as ButtonIconProps;
5862
}
5963
return undefined;
60-
}, [onBack, backButtonProps]);
64+
}, [onBack, backButtonProps, startButtonIconProps]);
6165

6266
// Build the endButtonIconProps array with close button if needed
6367
const resolvedEndButtonIconProps = useMemo(() => {

app/component-library/components-temp/HeaderCenter/HeaderCenter.types.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ import { HeaderBaseProps } from '../../components/HeaderBase';
1010
/**
1111
* HeaderCenter component props.
1212
*/
13-
export interface HeaderCenterProps
14-
extends Omit<HeaderBaseProps, 'startButtonIconProps'> {
13+
export interface HeaderCenterProps extends HeaderBaseProps {
1514
/**
1615
* Title text to display in the header.
1716
* Used as children if children prop is not provided.

app/component-library/components-temp/HeaderWithTitleLeft/HeaderWithTitleLeft.test.tsx

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const TEST_IDS = {
1010
CONTAINER: 'header-with-title-left-container',
1111
TITLE_SECTION: 'header-with-title-left-title-section',
1212
BACK_BUTTON: 'header-with-title-left-back-button',
13+
CLOSE_BUTTON: 'header-with-title-left-close-button',
1314
TITLE_LEFT: 'title-left',
1415
HEADER_BASE_END_ACCESSORY: 'header-base-end-accessory',
1516
};
@@ -169,6 +170,88 @@ describe('HeaderWithTitleLeft', () => {
169170
});
170171
});
171172

173+
describe('close button', () => {
174+
it('renders close button when onClose provided', () => {
175+
const { getByTestId } = render(
176+
<HeaderWithTitleLeft
177+
onClose={jest.fn()}
178+
closeButtonProps={{ testID: TEST_IDS.CLOSE_BUTTON }}
179+
titleLeftProps={{ title: 'Test' }}
180+
/>,
181+
);
182+
183+
expect(getByTestId(TEST_IDS.CLOSE_BUTTON)).toBeOnTheScreen();
184+
});
185+
186+
it('renders close button when closeButtonProps provided', () => {
187+
const { getByTestId } = render(
188+
<HeaderWithTitleLeft
189+
closeButtonProps={{
190+
onPress: jest.fn(),
191+
testID: TEST_IDS.CLOSE_BUTTON,
192+
}}
193+
titleLeftProps={{ title: 'Test' }}
194+
/>,
195+
);
196+
197+
expect(getByTestId(TEST_IDS.CLOSE_BUTTON)).toBeOnTheScreen();
198+
});
199+
200+
it('calls onClose when close button pressed', () => {
201+
const onClose = jest.fn();
202+
const { getByTestId } = render(
203+
<HeaderWithTitleLeft
204+
onClose={onClose}
205+
closeButtonProps={{ testID: TEST_IDS.CLOSE_BUTTON }}
206+
titleLeftProps={{ title: 'Test' }}
207+
/>,
208+
);
209+
210+
fireEvent.press(getByTestId(TEST_IDS.CLOSE_BUTTON));
211+
212+
expect(onClose).toHaveBeenCalledTimes(1);
213+
});
214+
215+
it('calls closeButtonProps.onPress when close button pressed', () => {
216+
const onPress = jest.fn();
217+
const { getByTestId } = render(
218+
<HeaderWithTitleLeft
219+
closeButtonProps={{ onPress, testID: TEST_IDS.CLOSE_BUTTON }}
220+
titleLeftProps={{ title: 'Test' }}
221+
/>,
222+
);
223+
224+
fireEvent.press(getByTestId(TEST_IDS.CLOSE_BUTTON));
225+
226+
expect(onPress).toHaveBeenCalledTimes(1);
227+
});
228+
229+
it('closeButtonProps.onPress takes priority over onClose', () => {
230+
const onClose = jest.fn();
231+
const onPress = jest.fn();
232+
const { getByTestId } = render(
233+
<HeaderWithTitleLeft
234+
onClose={onClose}
235+
closeButtonProps={{ onPress, testID: TEST_IDS.CLOSE_BUTTON }}
236+
titleLeftProps={{ title: 'Test' }}
237+
/>,
238+
);
239+
240+
fireEvent.press(getByTestId(TEST_IDS.CLOSE_BUTTON));
241+
242+
expect(onPress).toHaveBeenCalledTimes(1);
243+
expect(onClose).not.toHaveBeenCalled();
244+
});
245+
246+
it('does not render close button when neither onClose nor closeButtonProps provided', () => {
247+
const { queryByLabelText } = render(
248+
<HeaderWithTitleLeft titleLeftProps={{ title: 'Test' }} />,
249+
);
250+
251+
expect(queryByLabelText('Close')).toBeNull();
252+
});
253+
});
254+
172255
describe('props forwarding', () => {
173256
it('forwards endButtonIconProps to HeaderBase', () => {
174257
const { getByTestId } = render(

app/component-library/components-temp/HeaderWithTitleLeft/HeaderWithTitleLeft.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,12 @@ import { HeaderWithTitleLeftProps } from './HeaderWithTitleLeft.types';
3232
const HeaderWithTitleLeft: React.FC<HeaderWithTitleLeftProps> = ({
3333
onBack,
3434
backButtonProps,
35+
onClose,
36+
closeButtonProps,
3537
titleLeft,
3638
titleLeftProps,
3739
startButtonIconProps,
40+
endButtonIconProps,
3841
twClassName,
3942
testID,
4043
titleSectionTestID,
@@ -59,6 +62,26 @@ const HeaderWithTitleLeft: React.FC<HeaderWithTitleLeftProps> = ({
5962
return undefined;
6063
}, [startButtonIconProps, onBack, backButtonProps]);
6164

65+
// Build endButtonIconProps with close button if onClose or closeButtonProps is provided
66+
const resolvedEndButtonIconProps = useMemo(() => {
67+
const props: ButtonIconProps[] = [];
68+
69+
if (onClose || closeButtonProps) {
70+
const closeProps: ButtonIconProps = {
71+
iconName: IconName.Close,
72+
...(closeButtonProps || {}),
73+
onPress: closeButtonProps?.onPress ?? onClose,
74+
};
75+
props.push(closeProps);
76+
}
77+
78+
if (endButtonIconProps) {
79+
props.push(...endButtonIconProps);
80+
}
81+
82+
return props.length > 0 ? props : undefined;
83+
}, [endButtonIconProps, onClose, closeButtonProps]);
84+
6285
// Render title section content
6386
const renderTitleSection = () => {
6487
if (titleLeft) {
@@ -79,6 +102,7 @@ const HeaderWithTitleLeft: React.FC<HeaderWithTitleLeftProps> = ({
79102
{/* HeaderBase section */}
80103
<HeaderBase
81104
startButtonIconProps={resolvedStartButtonIconProps}
105+
endButtonIconProps={resolvedEndButtonIconProps}
82106
twClassName={resolvedTwClassName}
83107
{...headerBaseProps}
84108
/>

app/component-library/components-temp/HeaderWithTitleLeft/HeaderWithTitleLeft.types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ export interface HeaderWithTitleLeftProps
2323
* If provided, a back button will be rendered with these props spread.
2424
*/
2525
backButtonProps?: Omit<ButtonIconProps, 'iconName'>;
26+
/**
27+
* Callback when the close button is pressed.
28+
* If provided, a close button will be added to endButtonIconProps.
29+
*/
30+
onClose?: () => void;
31+
/**
32+
* Additional props to pass to the close ButtonIcon.
33+
* If provided, a close button will be added to endButtonIconProps with these props spread.
34+
*/
35+
closeButtonProps?: Omit<ButtonIconProps, 'iconName'>;
2636
/**
2737
* Custom node to render in the title section.
2838
* If provided, takes priority over titleLeftProps.

app/component-library/components-temp/HeaderWithTitleLeftScrollable/HeaderWithTitleLeftScrollable.test.tsx

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { render, renderHook } from '@testing-library/react-native';
44
import { useSharedValue, SharedValue } from 'react-native-reanimated';
55
import { Text } from 'react-native';
66

7+
// External dependencies.
8+
import { IconName } from '@metamask/design-system-react-native';
9+
710
// Internal dependencies.
811
import HeaderWithTitleLeftScrollable from './HeaderWithTitleLeftScrollable';
912
import useHeaderWithTitleLeftScrollable from './useHeaderWithTitleLeftScrollable';
@@ -22,6 +25,11 @@ jest.mock('react-native-reanimated', () => {
2225
return Reanimated;
2326
});
2427

28+
// Mock react-native-safe-area-context
29+
jest.mock('react-native-safe-area-context', () => ({
30+
useSafeAreaInsets: () => ({ top: 44, bottom: 34, left: 0, right: 0 }),
31+
}));
32+
2533
// Test wrapper component that provides scrollY
2634
const TestWrapper: React.FC<{
2735
children: (scrollYValue: SharedValue<number>) => React.ReactNode;
@@ -106,6 +114,114 @@ describe('HeaderWithTitleLeftScrollable', () => {
106114
});
107115
});
108116

117+
describe('close button', () => {
118+
it('renders close button when onClose provided', () => {
119+
const { getByTestId } = render(
120+
<TestWrapper>
121+
{(scrollYValue) => (
122+
<HeaderWithTitleLeftScrollable
123+
title="Test"
124+
scrollY={scrollYValue}
125+
onClose={jest.fn()}
126+
closeButtonProps={{ testID: 'test-close-button' }}
127+
/>
128+
)}
129+
</TestWrapper>,
130+
);
131+
132+
expect(getByTestId('test-close-button')).toBeOnTheScreen();
133+
});
134+
135+
it('renders close button when closeButtonProps provided', () => {
136+
const { getByTestId } = render(
137+
<TestWrapper>
138+
{(scrollYValue) => (
139+
<HeaderWithTitleLeftScrollable
140+
title="Test"
141+
scrollY={scrollYValue}
142+
closeButtonProps={{
143+
onPress: jest.fn(),
144+
testID: 'test-close-button',
145+
}}
146+
/>
147+
)}
148+
</TestWrapper>,
149+
);
150+
151+
expect(getByTestId('test-close-button')).toBeOnTheScreen();
152+
});
153+
});
154+
155+
describe('endButtonIconProps', () => {
156+
it('renders endButtonIconProps', () => {
157+
const { getByTestId } = render(
158+
<TestWrapper>
159+
{(scrollYValue) => (
160+
<HeaderWithTitleLeftScrollable
161+
title="Test"
162+
scrollY={scrollYValue}
163+
endButtonIconProps={[
164+
{
165+
iconName: IconName.Close,
166+
onPress: jest.fn(),
167+
testID: 'end-button',
168+
},
169+
]}
170+
/>
171+
)}
172+
</TestWrapper>,
173+
);
174+
175+
expect(getByTestId('end-button')).toBeOnTheScreen();
176+
});
177+
});
178+
179+
describe('isInsideSafeAreaView', () => {
180+
it('positions header at top 0 when isInsideSafeAreaView is false', () => {
181+
const { getByTestId } = render(
182+
<TestWrapper>
183+
{(scrollYValue) => (
184+
<HeaderWithTitleLeftScrollable
185+
title="Test"
186+
scrollY={scrollYValue}
187+
isInsideSafeAreaView={false}
188+
testID="test-container"
189+
/>
190+
)}
191+
</TestWrapper>,
192+
);
193+
194+
const container = getByTestId('test-container');
195+
const flattenedStyle = Array.isArray(container.props.style)
196+
? Object.assign({}, ...container.props.style)
197+
: container.props.style;
198+
199+
expect(flattenedStyle.top).toBe(0);
200+
});
201+
202+
it('positions header at insets.top when isInsideSafeAreaView is true', () => {
203+
const { getByTestId } = render(
204+
<TestWrapper>
205+
{(scrollYValue) => (
206+
<HeaderWithTitleLeftScrollable
207+
title="Test"
208+
scrollY={scrollYValue}
209+
isInsideSafeAreaView
210+
testID="test-container"
211+
/>
212+
)}
213+
</TestWrapper>,
214+
);
215+
216+
const container = getByTestId('test-container');
217+
const flattenedStyle = Array.isArray(container.props.style)
218+
? Object.assign({}, ...container.props.style)
219+
: container.props.style;
220+
221+
expect(flattenedStyle.top).toBe(44);
222+
});
223+
});
224+
109225
describe('titleLeft and titleLeftProps', () => {
110226
it('renders custom titleLeft node', () => {
111227
const { getByText } = render(

0 commit comments

Comments
 (0)