Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
dcd5f11
Initial working version
bfullam Feb 25, 2026
3044394
Fix trending tokens sort layout
bfullam Feb 25, 2026
a670e7b
Reduce BridgeView diff noise
bfullam Feb 25, 2026
34f748d
Remove already sorted bridge logic
bfullam Feb 25, 2026
a060a2e
Simplify Bridge chunk loading logic
bfullam Feb 25, 2026
6c5786a
Simplify bridge chunk loading guards
bfullam Feb 25, 2026
1eeae07
Merge remote-tracking branch 'origin/main' into swaps-4038-trending-t…
bfullam Feb 25, 2026
49154c9
Merge branch 'main' into swaps-4038-trending-tokens
bfullam Feb 26, 2026
0f5b98f
Fix bottom sheet layering issue
bfullam Feb 26, 2026
fa07482
Fix bottom sheet positioning
bfullam Feb 26, 2026
e22e322
Fix bottom sheet layering
bfullam Feb 26, 2026
98f5ff3
Fix bottom sheet overlay issue
bfullam Feb 26, 2026
7345e07
Fix bottom sheet placement
bfullam Feb 26, 2026
39fda97
Fix bottom sheet rendering
bfullam Feb 26, 2026
6ac722b
Fix bridge bottom sheet jitter
bfullam Feb 26, 2026
fb3108b
Fix bottom sheet layering jitter
bfullam Feb 26, 2026
ddc3059
Evaluate Trending List feature
bfullam Feb 27, 2026
2797563
Merge branch 'main' into swaps-4038-trending-tokens
bfullam Feb 27, 2026
a88f454
Merge branch 'main' into swaps-4038-trending-tokens
bfullam Feb 27, 2026
97eaf9e
Resolve BridgeView test errors
bfullam Feb 27, 2026
0584f54
Fix Bridge UI tests errors
bfullam Feb 27, 2026
3dcaa95
feat: add LD feature flag gate
bfullam Mar 2, 2026
cac8dcf
Update bridge component networks
bfullam Mar 2, 2026
ac6e4fc
Merge branch 'main' into swaps-4038-trending-tokens
bfullam Mar 2, 2026
e190d41
Merge branch 'main' into swaps-4038-trending-tokens
bfullam Mar 2, 2026
a128a45
Merge branch 'main' into swaps-4038-trending-tokens
bfullam Mar 2, 2026
a295f3b
Merge branch 'main' into swaps-4038-trending-tokens
bfullam Mar 2, 2026
51730d2
Investigate Bridge test failure
bfullam Mar 2, 2026
ffee6e5
Use shared FilterButton in bridge
bfullam Mar 2, 2026
40baecc
test: add e2e coverage
bfullam Mar 3, 2026
b837b83
Fix Bridge show more opacity
bfullam Mar 3, 2026
9591b5f
Merge remote-tracking branch 'origin/main' into swaps-4038-trending-t…
bfullam Mar 3, 2026
39cb2ce
chore: move swap trending e2e coverage to follow-up PR
bfullam Mar 3, 2026
7e90d12
test: add smoke swap trending tokens e2e coverage
bfullam Mar 3, 2026
18be11f
test: add smoke swap trending tokens e2e coverage (#26910)
bfullam Mar 6, 2026
73b52d4
Revert "test: add smoke swap trending tokens e2e coverage"
bfullam Mar 6, 2026
ab8ffe2
Revert "test: add smoke swap trending tokens e2e coverage" (#27107)
bfullam Mar 6, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,12 @@ export const createStyles = (params: { theme: Theme }) => {
backgroundColor: theme.colors.background.default,
},
quoteContainer: {
flex: 1,
justifyContent: 'flex-start',
},
destinationAccountSelectorContainer: {
paddingBottom: 12,
},
dynamicContent: {
flex: 1,
justifyContent: 'flex-start',
},
keypadContainerWithDestinationPicker: {
Expand All @@ -42,6 +40,10 @@ export const createStyles = (params: { theme: Theme }) => {
},
scrollViewContent: {
flexGrow: 1,
paddingBottom: 16,
},
Comment thread
cursor[bot] marked this conversation as resolved.
loadingContainer: {
paddingTop: 8,
},
disclaimerText: {
textAlign: 'center',
Expand Down
192 changes: 171 additions & 21 deletions app/components/UI/Bridge/Views/BridgeView/BridgeView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import { useRWAToken } from '../../hooks/useRWAToken';
import { strings } from '../../../../../../locales/i18n';
import { isHardwareAccount } from '../../../../../util/address';
import { BridgeViewSelectorsIDs } from './BridgeView.testIds';
import { MOCK_ENTROPY_SOURCE as mockEntropySource } from '../../../../../util/test/keyringControllerTestUtils';
import { RootState } from '../../../../../reducers';
import { mockQuoteWithMetadata } from '../../_mocks_/bridgeQuoteWithMetadata';
Expand Down Expand Up @@ -281,6 +282,27 @@
};
});

jest.mock(
'../../components/BridgeTrendingZeroState/BridgeTrendingZeroState',
() => {
const React = jest.requireActual('react');
const { View } = jest.requireActual('react-native');
const { BridgeViewSelectorsIDs: BridgeViewTestIds } = jest.requireActual(
'./BridgeView.testIds',
);

return {
__esModule: true,
default: React.forwardRef(
(_props: Record<string, never>, _ref: React.Ref<unknown>) =>
React.createElement(View, {
testID: BridgeViewTestIds.TRENDING_TOKENS_SECTION,
}),
),
};
},
);

// Mock BottomSheetDialog so that onCloseDialog synchronously calls onClose,
// allowing keypad close() to work in tests (the real component uses reanimated
// withTiming which never completes in JSDOM).
Expand Down Expand Up @@ -331,16 +353,19 @@
jest.clearAllMocks();
});

it('renders', async () => {
const { toJSON } = renderScreen(
it('renders source and destination token areas', async () => {
const { getByTestId } = renderScreen(
BridgeView,
{
name: Routes.BRIDGE.ROOT,
},
{ state: mockState },
);

expect(toJSON()).toMatchSnapshot();
expect(getByTestId(BridgeViewSelectorsIDs.SOURCE_TOKEN_AREA)).toBeTruthy();
expect(
getByTestId(BridgeViewSelectorsIDs.DESTINATION_TOKEN_AREA),
).toBeTruthy();
Comment thread
cursor[bot] marked this conversation as resolved.
});

it('should open BridgeTokenSelector when clicking source token', async () => {
Expand Down Expand Up @@ -393,7 +418,12 @@
{ state: mockState },
);

// Verify keypad is open (opened by useBridgeViewOnFocus on mount)
const sourceInput = getByTestId('source-token-area-input');
await act(async () => {
sourceInput.props.onPressIn();
});

// Verify keypad is open
await waitFor(() => {
expect(getByText('1')).toBeTruthy();
expect(queryByTestId('keypad-delete-button')).toBeTruthy();
Expand Down Expand Up @@ -434,6 +464,11 @@
{ state: mockState },
);

const sourceInput = getByTestId('source-token-area-input');
await act(async () => {
sourceInput.props.onPressIn();
});

// Press number buttons to input
fireEvent.press(getByText('9'));
fireEvent.press(getByText('.'));
Expand Down Expand Up @@ -755,21 +790,19 @@
.mockImplementation(() => mockUseBridgeQuoteData);
});

it('displays keypad when no amount is entered', () => {
const { getByText } = renderScreen(
it('does not display keypad on initial render when no amount is entered', () => {
const { queryByTestId } = renderScreen(
BridgeView,
{
name: Routes.BRIDGE.ROOT,
},
{ state: mockState },
);

// Keypad is visible instead of "Select amount" text
expect(getByText('1')).toBeTruthy();
expect(getByText('5')).toBeTruthy();
expect(queryByTestId('keypad-delete-button')).toBeNull();
});

it('displays keypad when amount is zero', () => {
it('does not display keypad on initial render when amount is zero', () => {
const stateWithZeroAmount = {
...mockState,
bridge: {
Expand All @@ -778,20 +811,18 @@
},
};

const { getByText } = renderScreen(
const { queryByTestId } = renderScreen(
BridgeView,
{
name: Routes.BRIDGE.ROOT,
},
{ state: stateWithZeroAmount },
);

// Keypad is visible instead of "Select amount" text
expect(getByText('1')).toBeTruthy();
expect(getByText('5')).toBeTruthy();
expect(queryByTestId('keypad-delete-button')).toBeNull();
});

it('displays "Fetching quote" when quotes are loading and there is no active quote', () => {
it('shows loading mode with quote skeleton only', () => {
const testState = createBridgeTestState({
bridgeControllerOverrides: {
quotesLastFetched: null,
Expand All @@ -806,15 +837,135 @@
activeQuote: null,
}));

const { getByText } = renderScreen(
const { getByTestId, queryByTestId, queryByText } = renderScreen(
BridgeView,
{
name: Routes.BRIDGE.ROOT,
},
{ state: testState },
);

expect(
getByTestId(BridgeViewSelectorsIDs.QUOTE_DETAILS_SKELETON),
).toBeTruthy();
expect(queryByTestId('banneralert')).toBeNull();
expect(queryByTestId('edit-slippage-button')).toBeNull();
expect(
queryByTestId(BridgeViewSelectorsIDs.TRENDING_TOKENS_SECTION),
).toBeNull();
expect(queryByText('Fetching quote')).toBeNull();
});

it('shows error mode with banner and without quote or zero state', async () => {
const testState = createBridgeTestState({
bridgeControllerOverrides: {
quotesLoadingStatus: RequestStatus.FETCHED,
quotes: [],
quotesLastFetched: 12,
},
});

jest
.mocked(useBridgeQuoteData as unknown as jest.Mock)
.mockImplementation(() => ({
...mockUseBridgeQuoteData,
activeQuote: null,
isLoading: false,
quoteFetchError: 'Error fetching quote',
isNoQuotesAvailable: true,
}));

const { queryByTestId } = renderScreen(
BridgeView,
{
name: Routes.BRIDGE.ROOT,
},
{ state: testState },
);

await waitFor(() => {
expect(queryByTestId('banneralert')).toBeTruthy();
});
expect(queryByTestId('edit-slippage-button')).toBeNull();
expect(
queryByTestId(BridgeViewSelectorsIDs.TRENDING_TOKENS_SECTION),
).toBeNull();
});

it('shows quote mode with quote content and confirm button', async () => {
const now = Date.now();
const testState = createBridgeTestState({
bridgeControllerOverrides: {
quotesLoadingStatus: RequestStatus.FETCHED,
quotes: [mockQuoteWithMetadata as unknown as QuoteResponse],
recommendedQuote: mockQuoteWithMetadata as unknown as QuoteResponse,

Check failure on line 901 in app/components/UI/Bridge/Views/BridgeView/BridgeView.test.tsx

View workflow job for this annotation

GitHub Actions / scripts (lint:tsc)

Object literal may only specify known properties, and 'recommendedQuote' does not exist in type 'Partial<BridgeControllerState>'.
quotesLastFetched: now,
},
bridgeReducerOverrides: {
sourceAmount: '1.0',
},
});

jest
.mocked(useBridgeQuoteData as unknown as jest.Mock)
.mockImplementation(() => ({
...mockUseBridgeQuoteData,
isLoading: false,
activeQuote: mockQuoteWithMetadata as unknown as QuoteResponse,
}));

const { getByTestId, queryByTestId } = renderScreen(
BridgeView,
{
name: Routes.BRIDGE.ROOT,
},
{ state: testState },
);

await waitFor(() => {
expect(queryByTestId('edit-slippage-button')).toBeTruthy();
});
expect(getByTestId(BridgeViewSelectorsIDs.CONFIRM_BUTTON)).toBeTruthy();
expect(
queryByTestId(BridgeViewSelectorsIDs.TRENDING_TOKENS_SECTION),
).toBeNull();
});

it('shows zero mode with trending section and without quote content', () => {
const testState = createBridgeTestState({
bridgeControllerOverrides: {
quotesLoadingStatus: RequestStatus.FETCHED,
quotes: [],
quotesLastFetched: 12,
},
bridgeReducerOverrides: {
sourceAmount: undefined,
},
});

jest
.mocked(useBridgeQuoteData as unknown as jest.Mock)
.mockImplementation(() => ({
...mockUseBridgeQuoteData,
activeQuote: null,
isLoading: false,
quoteFetchError: null,
isNoQuotesAvailable: false,
destTokenAmount: undefined,
}));

const { getByTestId, queryByTestId } = renderScreen(
BridgeView,
{
name: Routes.BRIDGE.ROOT,
},
{ state: testState },
);

expect(getByText('Fetching quote')).toBeTruthy();
expect(
getByTestId(BridgeViewSelectorsIDs.TRENDING_TOKENS_SECTION),
).toBeTruthy();
expect(queryByTestId('edit-slippage-button')).toBeNull();
});

it('navigates to QuoteExpiredModal when quote expires without refresh', async () => {
Expand Down Expand Up @@ -932,7 +1083,7 @@
});
});

it('blurs input when opening QuoteExpiredModal', async () => {
it('navigates to QuoteExpiredModal when quote expires and leaves quote content hidden', async () => {
jest
.mocked(useBridgeQuoteData as unknown as jest.Mock)
.mockImplementation(() => ({
Expand All @@ -943,7 +1094,7 @@
activeQuote: undefined, // activeQuote is undefined when quote expires without refresh
}));

const { toJSON } = renderScreen(
const { queryByTestId } = renderScreen(
BridgeView,
{
name: Routes.BRIDGE.ROOT,
Expand All @@ -956,8 +1107,7 @@
screen: Routes.BRIDGE.MODALS.QUOTE_EXPIRED_MODAL,
});
});

expect(toJSON()).toMatchSnapshot();
expect(queryByTestId('edit-slippage-button')).toBeNull();
});

it('displays hardware wallet not supported banner when using hardware wallet with Solana source', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ export const BridgeViewSelectorsIDs = {
CONFIRM_BUTTON: 'bridge-confirm-button',
CONFIRM_BUTTON_KEYPAD: 'bridge-confirm-button-keypad',
BRIDGE_VIEW_SCROLL: 'bridge-view-scroll',
TRENDING_TOKENS_SECTION: 'bridge-trending-tokens-section',
TRENDING_PRICE_FILTER: 'bridge-trending-price-filter',
TRENDING_NETWORK_FILTER: 'bridge-trending-network-filter',
TRENDING_TIME_FILTER: 'bridge-trending-time-filter',
TRENDING_SHOW_MORE: 'bridge-trending-show-more',
QUOTE_DETAILS_SKELETON: 'bridge-quote-details-skeleton',
} as const;

export type BridgeViewSelectorsIDsType = typeof BridgeViewSelectorsIDs;
Loading
Loading