Skip to content

Commit 3ac4c6e

Browse files
GeorgeGkasdavibroc
andauthored
fix: disable slide-to-dismiss behavior of swaps keypad (MetaMask#26770)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** Disable slide-to-dismiss behavior of swaps keypad to prevent potential double triggers of keypad keys due to bottom sheet dismissal gesture handler event. <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: disable slide-to-dismiss behavior of swaps keypad ## **Related issues** Fixes: MetaMask#26746 ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes bottom-sheet gesture behavior for the swap/bridge keypad, which could affect how users dismiss the keypad and interact with inputs. Test updates reduce flakiness but rely on mocked bridge status responses and new timeouts that could mask regressions. > > **Overview** > Disables the `SwapsKeypad` slide/pan interaction by setting `BottomSheetDialog` `isInteractable={false}` to prevent gesture-handler double-firing of keypad button presses. > > Updates `SwapsKeypad` unit tests to capture and assert the `BottomSheetDialog` configuration, and adjusts bridge smoke-test stability by replacing a fixed delay with visibility assertions, adding confirmed-status verification, and mocking `getTxStatus` to return a complete bridge status response. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 0724794. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Davide Brocchetto <davide.brocchetto@consensys.net>
1 parent 33cfed3 commit 3ac4c6e

4 files changed

Lines changed: 71 additions & 6 deletions

File tree

app/components/UI/Bridge/components/SwapsKeypad/index.test.tsx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { SwapsKeypad } from './index';
44
import { Keys } from '../../../../Base/Keypad';
55
import { SwapsKeypadRef } from './types';
66

7+
// Captures props passed to BottomSheetDialog so tests can assert configuration.
8+
const mockBottomSheetDialogProps = jest.fn();
9+
710
// Mock BottomSheetDialog to render children directly without animations.
811
// Exposes onCloseDialog via ref so close() can be tested.
912
jest.mock(
@@ -17,9 +20,16 @@ jest.mock(
1720
{
1821
children,
1922
onClose,
20-
}: { children: React.ReactNode; onClose?: () => void },
23+
...rest
24+
}: {
25+
children: React.ReactNode;
26+
onClose?: () => void;
27+
isInteractable?: boolean;
28+
[key: string]: unknown;
29+
},
2130
dialogRef: React.Ref<{ onCloseDialog: () => void }>,
2231
) => {
32+
mockBottomSheetDialogProps({ onClose, ...rest });
2333
MockReact.useImperativeHandle(dialogRef, () => ({
2434
onCloseDialog: () => onClose?.(),
2535
}));
@@ -53,6 +63,7 @@ describe('SwapsKeypad', () => {
5363

5464
afterEach(() => {
5565
jest.resetAllMocks();
66+
mockBottomSheetDialogProps.mockReset();
5667
});
5768

5869
describe('rendering', () => {
@@ -388,4 +399,19 @@ describe('SwapsKeypad', () => {
388399
expect(ref.current?.isOpen()).toBe(true);
389400
});
390401
});
402+
403+
describe('bottom sheet configuration', () => {
404+
it('renders BottomSheetDialog with isInteractable=false to prevent PanGestureHandler double-firing keypad buttons', () => {
405+
renderAndOpen({
406+
value: '0',
407+
currency: 'native',
408+
decimals: 18,
409+
onChange: mockOnChange,
410+
});
411+
412+
expect(mockBottomSheetDialogProps).toHaveBeenCalledWith(
413+
expect.objectContaining({ isInteractable: false }),
414+
);
415+
});
416+
});
391417
});

app/components/UI/Bridge/components/SwapsKeypad/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export const SwapsKeypad = forwardRef<
5959
<BottomSheetDialog
6060
style={styles.keypadDialog}
6161
ref={bottomSheetRef}
62-
isInteractable
62+
isInteractable={false}
6363
onClose={handleClose}
6464
onStartShouldSetResponder={() =>
6565
// Prevents the native gesture system from bubbling up

tests/helpers/swap/bridge-mocks.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,20 @@ import {
1515
} from './constants';
1616
import { setupSpotPricesMock } from './swap-mocks';
1717

18+
const BRIDGE_TX_STATUS_COMPLETE = {
19+
status: 'COMPLETE',
20+
srcChain: {
21+
chainId: 1,
22+
txHash:
23+
'0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890',
24+
},
25+
destChain: {
26+
chainId: 8453,
27+
txHash:
28+
'0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
29+
},
30+
};
31+
1832
export const testSpecificMock: TestSpecificMock = async (
1933
mockServer: Mockttp,
2034
) => {
@@ -126,4 +140,13 @@ export const testSpecificMock: TestSpecificMock = async (
126140
],
127141
responseCode: 200,
128142
});
143+
144+
// Mock getTxStatus with a full response so the BridgeStatusController
145+
// marks the bridge as complete (srcChain + destChain txHash present)
146+
await setupMockRequest(mockServer, {
147+
requestMethod: 'GET',
148+
url: /getTxStatus/i,
149+
response: BRIDGE_TX_STATUS_COMPLETE,
150+
responseCode: 200,
151+
});
129152
};

tests/smoke/swap/bridge-action-smoke.spec.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import TabBarComponent from '../../page-objects/wallet/TabBarComponent';
55
import QuoteView from '../../page-objects/swaps/QuoteView';
66
import FixtureBuilder from '../../framework/fixtures/FixtureBuilder';
77
import WalletView from '../../page-objects/wallet/WalletView';
8-
import TestHelpers from '../../helpers';
98
import { SmokeTrade } from '../../tags';
109
import Assertions from '../../framework/Assertions';
1110
import ActivitiesView from '../../page-objects/Transactions/ActivitiesView';
@@ -14,6 +13,7 @@ import { testSpecificMock } from '../../helpers/swap/bridge-mocks';
1413
import SoftAssert from '../../framework/SoftAssert';
1514
import { AnvilPort } from '../../framework/fixtures/FixtureUtils';
1615
import { AnvilManager } from '../../seeder/anvil-manager';
16+
import { ActivitiesViewSelectorsText } from '../../../app/components/Views/ActivityView/ActivitiesView.testIds';
1717

1818
enum eventsToCheck {
1919
BRIDGE_BUTTON_CLICKED = 'Bridge Button Clicked',
@@ -38,6 +38,7 @@ describe(SmokeTrade('Bridge functionality'), () => {
3838
const sourceSymbol: string = 'ETH';
3939
const chainId = '0x1';
4040
const destChainId = '0x2105';
41+
const FIRST_ROW: number = 0;
4142

4243
await withFixtures(
4344
{
@@ -78,7 +79,10 @@ describe(SmokeTrade('Bridge functionality'), () => {
7879
await WalletView.tapWalletSwapButton();
7980
await device.disableSynchronization();
8081
await QuoteView.tapDestinationToken();
81-
await TestHelpers.delay(2000); // wait until tokens are displayed
82+
await Assertions.expectElementToBeVisible(QuoteView.searchToken, {
83+
timeout: 15000,
84+
description: 'Token search input visible in destination token picker',
85+
});
8286
await QuoteView.selectNetwork(destNetwork);
8387
await QuoteView.tapToken(destChainId, sourceSymbol);
8488
// Open keypad by tapping source amount input (keypad is in BottomSheet, closed after token selection)
@@ -96,11 +100,23 @@ describe(SmokeTrade('Bridge functionality'), () => {
96100
await QuoteView.tapConfirmBridge();
97101

98102
await Assertions.expectElementToBeVisible(ActivitiesView.title, {
99-
description: 'Activity title visible',
103+
timeout: 30000,
104+
description: 'Activity title visible after bridge submission',
100105
});
101106
await Assertions.expectElementToBeVisible(
102107
ActivitiesView.bridgeActivityTitle(destNetwork),
103-
{ description: 'Bridge activity for destination network visible' },
108+
{
109+
description: 'Bridge activity for destination network visible',
110+
},
111+
);
112+
113+
await Assertions.expectElementToHaveText(
114+
ActivitiesView.transactionStatus(FIRST_ROW),
115+
ActivitiesViewSelectorsText.CONFIRM_TEXT,
116+
{
117+
timeout: 120000,
118+
description: 'Bridge transaction should show Confirmed status',
119+
},
104120
);
105121
},
106122
);

0 commit comments

Comments
 (0)