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
78 changes: 78 additions & 0 deletions ui/hooks/bridge/useTokenSearchResults.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { formatChainIdToCaip } from '@metamask/bridge-controller';
import { type CaipChainId } from '@metamask/utils';
import { waitFor } from '@testing-library/react';
import { renderHookWithProvider } from '../../../test/lib/render-helpers-navigate';
import { CHAIN_IDS } from '../../../shared/constants/network';
import {
Expand All @@ -7,6 +9,7 @@ import {
MOCK_EXTERNAL_SOLANA_ADDRESS,
} from '../../../test/data/bridge/mock-bridge-store';
import { getAccountGroupsByAddress } from '../../selectors/multichain-accounts/account-tree';
import { fetchTokensBySearchQuery } from '../../pages/bridge/utils/tokens';
import { useTokenSearchResults } from './useTokenSearchResults';

jest.mock('../../pages/bridge/utils/tokens', () => ({
Expand All @@ -22,7 +25,16 @@ jest.mock('../../store/actions', () => ({
getBearerToken: jest.fn().mockResolvedValue('mock-jwt'),
}));

const mockFetchTokensBySearchQuery =
fetchTokensBySearchQuery as jest.MockedFunction<
typeof fetchTokensBySearchQuery
>;

describe('useTokenSearchResults', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('does not crash when accountAddress is an external address with no matching account group', () => {
const mockStoreState = createBridgeMockStore({
featureFlagOverrides: {
Expand Down Expand Up @@ -100,4 +112,70 @@ describe('useTokenSearchResults', () => {
expect(result.current.isSearchResultsLoading).toBe(false);
expect(result.current.hasMoreResults).toBe(false);
});

it('refetches search results when chainIds change with the same search query', async () => {
const mockStoreState = createBridgeMockStore({
featureFlagOverrides: {
bridgeConfig: {
refreshRate: 30000,
maxRefreshCount: 5,
support: true,
chains: {
[CHAIN_IDS.MAINNET]: {
isActiveSrc: true,
isActiveDest: true,
},
[CHAIN_IDS.POLYGON]: {
isActiveSrc: true,
isActiveDest: true,
},
},
chainRanking: [
{ chainId: formatChainIdToCaip(CHAIN_IDS.MAINNET) },
{ chainId: formatChainIdToCaip(CHAIN_IDS.POLYGON) },
],
},
},
});

const mainnetChainId = formatChainIdToCaip(CHAIN_IDS.MAINNET);
const polygonChainId = formatChainIdToCaip(CHAIN_IDS.POLYGON);
let chainIds = new Set<CaipChainId>([mainnetChainId]);

const { rerender } = renderHookWithProvider(
() =>
useTokenSearchResults({
searchQuery: 'usdc',
// No owned assets match the query so stableMinimalAssetsString stays
// empty across network changes. chainIds must still trigger a refetch.
assetsToInclude: [],
chainIds,
}),
mockStoreState,
);

await waitFor(() => {
expect(mockFetchTokensBySearchQuery).toHaveBeenCalledTimes(1);
});
expect(mockFetchTokensBySearchQuery).toHaveBeenLastCalledWith(
expect.objectContaining({
chainIds: [mainnetChainId],
query: 'usdc',
}),
);

mockFetchTokensBySearchQuery.mockClear();
chainIds = new Set<CaipChainId>([polygonChainId]);
rerender();

await waitFor(() => {
expect(mockFetchTokensBySearchQuery).toHaveBeenCalledTimes(1);
});
expect(mockFetchTokensBySearchQuery).toHaveBeenLastCalledWith(
expect.objectContaining({
chainIds: [polygonChainId],
query: 'usdc',
}),
);
});
});
2 changes: 1 addition & 1 deletion ui/hooks/bridge/useTokenSearchResults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export const useTokenSearchResults = ({
// Debounce the initial fetch until the user stops typing
debouncedFetchSearchResults(searchQuery, filteredAssetsToInclude);
}
}, [searchQuery, stableMinimalAssetsString, jwt]);
}, [searchQuery, stableMinimalAssetsString, chainIds, jwt]);

useEffect(() => {
const debouncedFn = debouncedFetchSearchResults;
Expand Down
Loading