Skip to content
Draft
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
5 changes: 5 additions & 0 deletions app/components/Base/SelectorButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ const createStyles = (colors: Theme['colors']) =>
},
});

/**
* @deprecated Please update your code to use `SelectButton` from `@metamask/design-system-react-native`.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Marks the legacy Base SelectorButton deprecated now that Aggregator BuildQuote and AccountSelector no longer depend on it. Points new select triggers at MMDS SelectButton, consistent with the component-library SelectButton deprecation.

* The API may have changed — compare props before migrating.
* @see {@link https://github.com/MetaMask/metamask-design-system/blob/main/packages/design-system-react-native/src/components/SelectButton/README.md}
*/
const SelectorButton: React.FC<SelectorButtonProps & TouchableOpacityProps> = ({
onPress,
disabled,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import { Theme } from '../../../../../../util/theme/models';
import { StyleSheet } from 'react-native';

const styleSheet = (params: { theme: Theme }) => {
const { theme } = params;
const { colors } = theme;

return StyleSheet.create({
const styleSheet = (_params: { theme: Theme }) =>
StyleSheet.create({
viewContainer: {
flex: 1,
},
Expand All @@ -17,28 +14,12 @@ const styleSheet = (params: { theme: Theme }) => {
spacer: {
minWidth: 8,
},
keypadContainer: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
paddingBottom: 50,
backgroundColor: colors.background.section,
},
keypad: {
paddingHorizontal: 16,
},
cta: {
paddingTop: 12,
},
flexRow: {
flexDirection: 'row',
},
flagText: {
marginVertical: 3,
marginHorizontal: 0,
},
});
};

export default styleSheet;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { Limits, Payment } from '@consensys/on-ramp-sdk';
import { act, fireEvent, screen } from '@testing-library/react-native';
import { Pressable, Text } from 'react-native';
import { BackHandler, Pressable, Text } from 'react-native';
import type BN4 from 'bnjs4';
import { renderScreen } from '../../../../../../util/test/renderWithProvider';
import BuildQuote from './BuildQuote';
Expand Down Expand Up @@ -336,6 +336,7 @@ describe('BuildQuote View', () => {
mockPop.mockClear();
mockTrackEvent.mockClear();
jest.clearAllMocks();
jest.restoreAllMocks();
});

beforeEach(() => {
Expand Down Expand Up @@ -785,9 +786,15 @@ describe('BuildQuote View', () => {
mockUseFiatCurrenciesValues.currentFiatCurrency?.denomSymbol;
fireEvent.press(getByRoleButton(`${symbol}${initialAmount}`));
fireEvent.press(getByRoleButton(`${symbol}${quickAmount}`));
expect(getByRoleButton(`${symbol}${quickAmount}`)).toBeOnTheScreen();
expect(
screen.queryAllByRole('button', { name: `${symbol}${quickAmount}` }),
).toHaveLength(2);
screen.getByTestId(BuildQuoteSelectors.AMOUNT_KEYPAD_CONFIRM_BUTTON),
).toBeOnTheScreen();
expect(
screen.queryByRole('button', {
name: `${symbol}${mockUseLimitsInitialValues?.limits?.quickAmounts?.[1]}`,
}),
).not.toBeOnTheScreen();
});

it('validates the max limit', () => {
Expand Down Expand Up @@ -847,16 +854,162 @@ describe('BuildQuote View', () => {
).not.toBeOnTheScreen();

fireEvent.press(screen.getByTestId(BuildQuoteSelectors.AMOUNT_INPUT));
expect(
screen.getByTestId(BuildQuoteSelectors.AMOUNT_KEYPAD_BOTTOM_SHEET),
).toBeOnTheScreen();
expect(
screen.getByTestId(BuildQuoteSelectors.AMOUNT_INPUT_CURSOR),
).toBeOnTheScreen();

fireEvent.press(getByRoleButton('1'));
fireEvent.press(getByRoleButton('Done'));
expect(
screen.queryByTestId(BuildQuoteSelectors.AMOUNT_KEYPAD_BOTTOM_SHEET),
).not.toBeOnTheScreen();
expect(
screen.queryByTestId(BuildQuoteSelectors.AMOUNT_INPUT_CURSOR),
).not.toBeOnTheScreen();
});

it('replaces quick amounts with Done confirm when an amount is entered', () => {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Locks in the exclusive quick-amount vs Done slot for buy flow. After a digit is entered, presets must disappear and the confirm testID must appear so keypad height and CTA behavior stay aligned with Swaps.

render(BuildQuote);
const denomSymbol =
mockUseFiatCurrenciesValues.currentFiatCurrency?.denomSymbol;
const quickAmount =
mockUseLimitsInitialValues?.limits?.quickAmounts?.[0]?.toString();

fireEvent.press(getByRoleButton(`${denomSymbol}0`));

expect(getByRoleButton(`${denomSymbol}${quickAmount}`)).toBeOnTheScreen();
expect(
screen.queryByTestId(BuildQuoteSelectors.AMOUNT_KEYPAD_CONFIRM_BUTTON),
).not.toBeOnTheScreen();

fireEvent.press(getByRoleButton('1'));

expect(
screen.queryByRole('button', {
name: `${denomSymbol}${quickAmount}`,
}),
).not.toBeOnTheScreen();
expect(
screen.getByTestId(BuildQuoteSelectors.AMOUNT_KEYPAD_CONFIRM_BUTTON),
).toBeOnTheScreen();
});

it('dismisses the amount keypad when hardware back is pressed', () => {
let backPressHandler: (() => boolean | undefined) | undefined;
jest
.spyOn(BackHandler, 'addEventListener')
.mockImplementation((_event, handler) => {
backPressHandler = handler as () => boolean | undefined;
return { remove: jest.fn() };
});

render(BuildQuote);
const denomSymbol =
mockUseFiatCurrenciesValues.currentFiatCurrency?.denomSymbol;

fireEvent.press(getByRoleButton(`${denomSymbol}0`));
expect(
screen.getByTestId(BuildQuoteSelectors.AMOUNT_KEYPAD_BOTTOM_SHEET),
).toBeOnTheScreen();

let handled: boolean | undefined;
act(() => {
handled = backPressHandler?.();
});

expect(handled).toBe(true);
expect(
screen.queryByTestId(BuildQuoteSelectors.AMOUNT_KEYPAD_BOTTOM_SHEET),
).not.toBeOnTheScreen();
});

it('closes the amount keypad before opening the region selector', async () => {
render(BuildQuote);
const denomSymbol =
mockUseFiatCurrenciesValues.currentFiatCurrency?.denomSymbol;

fireEvent.press(getByRoleButton(`${denomSymbol}0`));
expect(
screen.getByTestId(BuildQuoteSelectors.AMOUNT_KEYPAD_BOTTOM_SHEET),
).toBeOnTheScreen();

await act(async () =>
fireEvent.press(
getByRoleButton(mockUseRegionsValues.selectedRegion?.emoji),
),
);

expect(
screen.queryByTestId(BuildQuoteSelectors.AMOUNT_KEYPAD_BOTTOM_SHEET),
).not.toBeOnTheScreen();
expect(mockNavigate).toHaveBeenCalledWith('RampModals', {
screen: 'RampRegionSelectorModal',
params: {
regions: mockRegionsData,
},
});
});

it('closes the amount keypad before opening the asset selector', () => {
render(BuildQuote);
const denomSymbol =
mockUseFiatCurrenciesValues.currentFiatCurrency?.denomSymbol;

fireEvent.press(getByRoleButton(`${denomSymbol}0`));
expect(
screen.getByTestId(BuildQuoteSelectors.AMOUNT_KEYPAD_BOTTOM_SHEET),
).toBeOnTheScreen();

fireEvent.press(getByRoleButton(mockCryptoCurrenciesData[0].name));

expect(
screen.queryByTestId(BuildQuoteSelectors.AMOUNT_KEYPAD_BOTTOM_SHEET),
).not.toBeOnTheScreen();
expect(mockNavigate).toHaveBeenCalledWith(
...createTokenSelectModalNavigationDetails({
tokens: mockCryptoCurrenciesData,
}),
);
});

it('closes the amount keypad before opening the payment method selector', () => {
render(BuildQuote);
const denomSymbol =
mockUseFiatCurrenciesValues.currentFiatCurrency?.denomSymbol;

fireEvent.press(getByRoleButton(`${denomSymbol}0`));
expect(
screen.getByTestId(BuildQuoteSelectors.AMOUNT_KEYPAD_BOTTOM_SHEET),
).toBeOnTheScreen();

fireEvent.press(getByRoleButton('Change'));

expect(
screen.queryByTestId(BuildQuoteSelectors.AMOUNT_KEYPAD_BOTTOM_SHEET),
).not.toBeOnTheScreen();
expect(mockNavigate).toHaveBeenCalledWith(
'RampModals',
expect.objectContaining({
screen: 'RampPaymentMethodSelectorModal',
}),
);
});

it('removes the hardware back listener on unmount', () => {
const mockRemove = jest.fn();
jest
.spyOn(BackHandler, 'addEventListener')
.mockReturnValue({ remove: mockRemove });

const { unmount } = render(BuildQuote);
unmount();

expect(mockRemove).toHaveBeenCalled();
});

it('does not reset the amount when switching assets in the buy flow', () => {
const AssetSwitcher = () => {
const [, setSelectedAssetVersion] = React.useState(0);
Expand Down Expand Up @@ -906,6 +1059,42 @@ describe('BuildQuote View', () => {
mockUseRampSDKValues.isSell = true;
});

it('opens the amount keypad bottom sheet when the sell amount input is pressed', () => {
render(BuildQuote);
const symbol = mockUseRampSDKValues.selectedAsset?.symbol;

expect(
screen.queryByTestId(BuildQuoteSelectors.AMOUNT_KEYPAD_BOTTOM_SHEET),
).not.toBeOnTheScreen();

fireEvent.press(getByRoleButton(`0 ${symbol}`));

expect(
screen.getByTestId(BuildQuoteSelectors.AMOUNT_KEYPAD_BOTTOM_SHEET),
).toBeOnTheScreen();
});

it('closes the amount keypad before opening the fiat selector in sell mode', () => {
render(BuildQuote);
const symbol = mockUseRampSDKValues.selectedAsset?.symbol;

fireEvent.press(getByRoleButton(`0 ${symbol}`));
expect(
screen.getByTestId(BuildQuoteSelectors.AMOUNT_KEYPAD_BOTTOM_SHEET),
).toBeOnTheScreen();

fireEvent.press(getByRoleButton(mockFiatCurrenciesData[0].symbol));

expect(
screen.queryByTestId(BuildQuoteSelectors.AMOUNT_KEYPAD_BOTTOM_SHEET),
).not.toBeOnTheScreen();
expect(mockNavigate).toHaveBeenCalledWith(
...createFiatSelectorModalNavigationDetails({
currencies: mockFiatCurrenciesData,
}),
);
});

it('updates the amount input', () => {
render(BuildQuote);
const initialAmount = '0';
Expand Down Expand Up @@ -992,14 +1181,56 @@ describe('BuildQuote View', () => {
fireEvent.press(getByRoleButton(`${initialAmount} ${symbol}`));
fireEvent.press(getByRoleButton('25%'));
expect(getByRoleButton(`0.25 ${symbol}`)).toBeTruthy();
expect(
screen.getByTestId(BuildQuoteSelectors.AMOUNT_KEYPAD_CONFIRM_BUTTON),
).toBeOnTheScreen();
expect(
screen.queryByRole('button', { name: 'Max' }),
).not.toBeOnTheScreen();
});

it('sets max amount from quick amount when amount is zero', () => {
render(BuildQuote);

fireEvent.press(getByRoleButton(`0.25 ${symbol}`));
mockUseBalanceValues.balanceBN = toTokenMinimalUnit(
'1',
mockUseRampSDKValues.selectedAsset?.decimals || 18,
) as BN4;
const symbol = mockUseRampSDKValues.selectedAsset?.symbol;
fireEvent.press(getByRoleButton(`0 ${symbol}`));
fireEvent.press(getByRoleButton('Max'));
expect(getByRoleButton(`1 ${symbol}`)).toBeTruthy();
expect(
screen.getByTestId(BuildQuoteSelectors.AMOUNT_KEYPAD_CONFIRM_BUTTON),
).toBeOnTheScreen();
});

it('updates the amount input up to the max considering gas for native asset', () => {
it('replaces quick amounts with Done confirm when a sell amount is entered', () => {
mockUseBalanceValues.balanceBN = toTokenMinimalUnit(
'1',
mockUseRampSDKValues.selectedAsset?.decimals || 18,
) as BN4;
render(BuildQuote);
const symbol = mockUseRampSDKValues.selectedAsset?.symbol;

fireEvent.press(getByRoleButton(`0 ${symbol}`));

expect(getByRoleButton('25%')).toBeOnTheScreen();
expect(
screen.queryByTestId(BuildQuoteSelectors.AMOUNT_KEYPAD_CONFIRM_BUTTON),
).not.toBeOnTheScreen();

fireEvent.press(getByRoleButton('1'));

expect(
screen.queryByRole('button', { name: '25%' }),
).not.toBeOnTheScreen();
expect(
screen.getByTestId(BuildQuoteSelectors.AMOUNT_KEYPAD_CONFIRM_BUTTON),
).toBeOnTheScreen();
});

it('updates the amount input up to the max considering gas for native asset', () => {
const initialAmount = '0';
const quickAmount = 'Max';
mockUseRampSDKValues = {
Expand All @@ -1026,14 +1257,14 @@ describe('BuildQuote View', () => {
mockUseRampSDKValues.selectedAsset?.decimals || 18,
) as BN4,
};
render(BuildQuote);
const symbol = mockUseRampSDKValues.selectedAsset?.symbol;
fireEvent.press(getByRoleButton(`${initialAmount} ${symbol}`));
fireEvent.press(getByRoleButton(quickAmount));
expect(getByRoleButton(`0.73 ${symbol}`)).toBeTruthy();
});

it('updates the amount input up to the percentage considering gas', () => {
render(BuildQuote);
const initialAmount = '0';
mockUseRampSDKValues = {
...mockUseRampSDKInitialValues,
Expand All @@ -1059,6 +1290,7 @@ describe('BuildQuote View', () => {
mockUseRampSDKValues.selectedAsset?.decimals || 18,
) as BN4,
};
render(BuildQuote);
const symbol = mockUseRampSDKValues.selectedAsset?.symbol;
fireEvent.press(getByRoleButton(`${initialAmount} ${symbol}`));
fireEvent.press(getByRoleButton('75%'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ export const BuildQuoteSelectors = {
MAX_LIMIT_ERROR: 'max-limit-error',
INSUFFICIENT_BALANCE_ERROR: 'insufficient-balance-error',
KEYPAD_DELETE_BUTTON: 'keypad-delete-button',
AMOUNT_KEYPAD_BOTTOM_SHEET: 'build-quote-amount-keypad-bottom-sheet',
AMOUNT_KEYPAD_CONFIRM_BUTTON: 'build-quote-amount-keypad-confirm-button',
};
Loading
Loading