Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,12 @@ certs/certificate.pem @MetaMask/mobile-adm
metro.transform.js @MetaMask/mobile-platform @MetaMask/core-platform

# Ramps Team
app/components/UI/Ramp/ @MetaMask/ramp
app/reducers/fiatOrders/ @MetaMask/ramp
app/components/UI/Ramp/ @MetaMask/ramp
app/reducers/fiatOrders/ @MetaMask/ramp
app/core/Engine/controllers/ramps-controller @MetaMask/ramp
app/core/Engine/messengers/ramps-controller-messenger @MetaMask/ramp
app/core/Engine/messengers/ramps-service-messenger @MetaMask/ramp
app/selectors/rampsController @MetaMask/ramp

# Card Team
app/components/UI/Card/ @MetaMask/card
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,42 @@ describe('HeaderCenter', () => {

expect(queryByTestId(START_ACCESSORY_TEST_ID)).not.toBeOnTheScreen();
});

it('renders startButtonIconProps when provided', () => {
const onPress = jest.fn();
const { getByTestId } = render(
<HeaderCenter
title="Title"
startButtonIconProps={{
iconName: IconName.Menu,
onPress,
testID: 'custom-start-button',
}}
/>,
);

expect(getByTestId('custom-start-button')).toBeOnTheScreen();
});

it('startButtonIconProps takes priority over onBack', () => {
const onBack = jest.fn();
const onPress = jest.fn();
const { getByTestId, queryByTestId } = render(
<HeaderCenter
title="Title"
onBack={onBack}
backButtonProps={{ testID: BACK_BUTTON_TEST_ID }}
startButtonIconProps={{
iconName: IconName.Menu,
onPress,
testID: 'custom-start-button',
}}
/>,
);

expect(getByTestId('custom-start-button')).toBeOnTheScreen();
expect(queryByTestId(BACK_BUTTON_TEST_ID)).not.toBeOnTheScreen();
});
});

describe('close button', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,16 @@ const HeaderCenter: React.FC<HeaderCenterProps> = ({
onClose,
closeButtonProps,
endButtonIconProps,
startButtonIconProps,
twClassName,
testID,
...headerBaseProps
}) => {
// Build the startButtonIconProps with back button if needed
const resolvedStartButtonIconProps = useMemo(() => {
if (startButtonIconProps) {
return startButtonIconProps;
}
if (onBack || backButtonProps) {
return {
iconName: IconName.ArrowLeft,
Expand All @@ -57,7 +61,7 @@ const HeaderCenter: React.FC<HeaderCenterProps> = ({
} as ButtonIconProps;
}
return undefined;
}, [onBack, backButtonProps]);
}, [onBack, backButtonProps, startButtonIconProps]);

// Build the endButtonIconProps array with close button if needed
const resolvedEndButtonIconProps = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import { HeaderBaseProps } from '../../components/HeaderBase';
/**
* HeaderCenter component props.
*/
export interface HeaderCenterProps
extends Omit<HeaderBaseProps, 'startButtonIconProps'> {
export interface HeaderCenterProps extends HeaderBaseProps {
/**
* Title text to display in the header.
* Used as children if children prop is not provided.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const TEST_IDS = {
CONTAINER: 'header-with-title-left-container',
TITLE_SECTION: 'header-with-title-left-title-section',
BACK_BUTTON: 'header-with-title-left-back-button',
CLOSE_BUTTON: 'header-with-title-left-close-button',
TITLE_LEFT: 'title-left',
HEADER_BASE_END_ACCESSORY: 'header-base-end-accessory',
};
Expand Down Expand Up @@ -169,6 +170,88 @@ describe('HeaderWithTitleLeft', () => {
});
});

describe('close button', () => {
it('renders close button when onClose provided', () => {
const { getByTestId } = render(
<HeaderWithTitleLeft
onClose={jest.fn()}
closeButtonProps={{ testID: TEST_IDS.CLOSE_BUTTON }}
titleLeftProps={{ title: 'Test' }}
/>,
);

expect(getByTestId(TEST_IDS.CLOSE_BUTTON)).toBeOnTheScreen();
});

it('renders close button when closeButtonProps provided', () => {
const { getByTestId } = render(
<HeaderWithTitleLeft
closeButtonProps={{
onPress: jest.fn(),
testID: TEST_IDS.CLOSE_BUTTON,
}}
titleLeftProps={{ title: 'Test' }}
/>,
);

expect(getByTestId(TEST_IDS.CLOSE_BUTTON)).toBeOnTheScreen();
});

it('calls onClose when close button pressed', () => {
const onClose = jest.fn();
const { getByTestId } = render(
<HeaderWithTitleLeft
onClose={onClose}
closeButtonProps={{ testID: TEST_IDS.CLOSE_BUTTON }}
titleLeftProps={{ title: 'Test' }}
/>,
);

fireEvent.press(getByTestId(TEST_IDS.CLOSE_BUTTON));

expect(onClose).toHaveBeenCalledTimes(1);
});

it('calls closeButtonProps.onPress when close button pressed', () => {
const onPress = jest.fn();
const { getByTestId } = render(
<HeaderWithTitleLeft
closeButtonProps={{ onPress, testID: TEST_IDS.CLOSE_BUTTON }}
titleLeftProps={{ title: 'Test' }}
/>,
);

fireEvent.press(getByTestId(TEST_IDS.CLOSE_BUTTON));

expect(onPress).toHaveBeenCalledTimes(1);
});

it('closeButtonProps.onPress takes priority over onClose', () => {
const onClose = jest.fn();
const onPress = jest.fn();
const { getByTestId } = render(
<HeaderWithTitleLeft
onClose={onClose}
closeButtonProps={{ onPress, testID: TEST_IDS.CLOSE_BUTTON }}
titleLeftProps={{ title: 'Test' }}
/>,
);

fireEvent.press(getByTestId(TEST_IDS.CLOSE_BUTTON));

expect(onPress).toHaveBeenCalledTimes(1);
expect(onClose).not.toHaveBeenCalled();
});

it('does not render close button when neither onClose nor closeButtonProps provided', () => {
const { queryByLabelText } = render(
<HeaderWithTitleLeft titleLeftProps={{ title: 'Test' }} />,
);

expect(queryByLabelText('Close')).toBeNull();
});
});

describe('props forwarding', () => {
it('forwards endButtonIconProps to HeaderBase', () => {
const { getByTestId } = render(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ import { HeaderWithTitleLeftProps } from './HeaderWithTitleLeft.types';
const HeaderWithTitleLeft: React.FC<HeaderWithTitleLeftProps> = ({
onBack,
backButtonProps,
onClose,
closeButtonProps,
titleLeft,
titleLeftProps,
startButtonIconProps,
endButtonIconProps,
twClassName,
testID,
titleSectionTestID,
Expand All @@ -59,6 +62,26 @@ const HeaderWithTitleLeft: React.FC<HeaderWithTitleLeftProps> = ({
return undefined;
}, [startButtonIconProps, onBack, backButtonProps]);

// Build endButtonIconProps with close button if onClose or closeButtonProps is provided
const resolvedEndButtonIconProps = useMemo(() => {
const props: ButtonIconProps[] = [];

if (onClose || closeButtonProps) {
const closeProps: ButtonIconProps = {
iconName: IconName.Close,
...(closeButtonProps || {}),
onPress: closeButtonProps?.onPress ?? onClose,
};
props.push(closeProps);
}

if (endButtonIconProps) {
props.push(...endButtonIconProps);
}

return props.length > 0 ? props : undefined;
}, [endButtonIconProps, onClose, closeButtonProps]);

// Render title section content
const renderTitleSection = () => {
if (titleLeft) {
Expand All @@ -79,6 +102,7 @@ const HeaderWithTitleLeft: React.FC<HeaderWithTitleLeftProps> = ({
{/* HeaderBase section */}
<HeaderBase
startButtonIconProps={resolvedStartButtonIconProps}
endButtonIconProps={resolvedEndButtonIconProps}
twClassName={resolvedTwClassName}
{...headerBaseProps}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ export interface HeaderWithTitleLeftProps
* If provided, a back button will be rendered with these props spread.
*/
backButtonProps?: Omit<ButtonIconProps, 'iconName'>;
/**
* Callback when the close button is pressed.
* If provided, a close button will be added to endButtonIconProps.
*/
onClose?: () => void;
/**
* Additional props to pass to the close ButtonIcon.
* If provided, a close button will be added to endButtonIconProps with these props spread.
*/
closeButtonProps?: Omit<ButtonIconProps, 'iconName'>;
/**
* Custom node to render in the title section.
* If provided, takes priority over titleLeftProps.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { render, renderHook } from '@testing-library/react-native';
import { useSharedValue, SharedValue } from 'react-native-reanimated';
import { Text } from 'react-native';

// External dependencies.
import { IconName } from '@metamask/design-system-react-native';

// Internal dependencies.
import HeaderWithTitleLeftScrollable from './HeaderWithTitleLeftScrollable';
import useHeaderWithTitleLeftScrollable from './useHeaderWithTitleLeftScrollable';
Expand All @@ -22,6 +25,11 @@ jest.mock('react-native-reanimated', () => {
return Reanimated;
});

// Mock react-native-safe-area-context
jest.mock('react-native-safe-area-context', () => ({
useSafeAreaInsets: () => ({ top: 44, bottom: 34, left: 0, right: 0 }),
}));

// Test wrapper component that provides scrollY
const TestWrapper: React.FC<{
children: (scrollYValue: SharedValue<number>) => React.ReactNode;
Expand Down Expand Up @@ -106,6 +114,114 @@ describe('HeaderWithTitleLeftScrollable', () => {
});
});

describe('close button', () => {
it('renders close button when onClose provided', () => {
const { getByTestId } = render(
<TestWrapper>
{(scrollYValue) => (
<HeaderWithTitleLeftScrollable
title="Test"
scrollY={scrollYValue}
onClose={jest.fn()}
closeButtonProps={{ testID: 'test-close-button' }}
/>
)}
</TestWrapper>,
);

expect(getByTestId('test-close-button')).toBeOnTheScreen();
});

it('renders close button when closeButtonProps provided', () => {
const { getByTestId } = render(
<TestWrapper>
{(scrollYValue) => (
<HeaderWithTitleLeftScrollable
title="Test"
scrollY={scrollYValue}
closeButtonProps={{
onPress: jest.fn(),
testID: 'test-close-button',
}}
/>
)}
</TestWrapper>,
);

expect(getByTestId('test-close-button')).toBeOnTheScreen();
});
});

describe('endButtonIconProps', () => {
it('renders endButtonIconProps', () => {
const { getByTestId } = render(
<TestWrapper>
{(scrollYValue) => (
<HeaderWithTitleLeftScrollable
title="Test"
scrollY={scrollYValue}
endButtonIconProps={[
{
iconName: IconName.Close,
onPress: jest.fn(),
testID: 'end-button',
},
]}
/>
)}
</TestWrapper>,
);

expect(getByTestId('end-button')).toBeOnTheScreen();
});
});

describe('isInsideSafeAreaView', () => {
it('positions header at top 0 when isInsideSafeAreaView is false', () => {
const { getByTestId } = render(
<TestWrapper>
{(scrollYValue) => (
<HeaderWithTitleLeftScrollable
title="Test"
scrollY={scrollYValue}
isInsideSafeAreaView={false}
testID="test-container"
/>
)}
</TestWrapper>,
);

const container = getByTestId('test-container');
const flattenedStyle = Array.isArray(container.props.style)
? Object.assign({}, ...container.props.style)
: container.props.style;

expect(flattenedStyle.top).toBe(0);
});

it('positions header at insets.top when isInsideSafeAreaView is true', () => {
const { getByTestId } = render(
<TestWrapper>
{(scrollYValue) => (
<HeaderWithTitleLeftScrollable
title="Test"
scrollY={scrollYValue}
isInsideSafeAreaView
testID="test-container"
/>
)}
</TestWrapper>,
);

const container = getByTestId('test-container');
const flattenedStyle = Array.isArray(container.props.style)
? Object.assign({}, ...container.props.style)
: container.props.style;

expect(flattenedStyle.top).toBe(44);
});
});

describe('titleLeft and titleLeftProps', () => {
it('renders custom titleLeft node', () => {
const { getByText } = render(
Expand Down
Loading
Loading