diff --git a/ui/components/multichain/network-list-menu/__snapshots__/network-list-menu.test.js.snap b/ui/components/multichain/network-list-menu/__snapshots__/network-list-menu.test.js.snap
new file mode 100644
index 000000000000..1b2a8d056105
--- /dev/null
+++ b/ui/components/multichain/network-list-menu/__snapshots__/network-list-menu.test.js.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`NetworkListMenu renders properly 1`] = `
`;
diff --git a/ui/components/multichain/network-list-menu/network-list-menu.js b/ui/components/multichain/network-list-menu/network-list-menu.js
index da7f145e7389..022ad7f055b0 100644
--- a/ui/components/multichain/network-list-menu/network-list-menu.js
+++ b/ui/components/multichain/network-list-menu/network-list-menu.js
@@ -37,11 +37,9 @@ import ToggleButton from '../../ui/toggle-button';
import {
AlignItems,
BackgroundColor,
- BlockSize,
Display,
FlexDirection,
JustifyContent,
- Size,
TextColor,
} from '../../../helpers/constants/design-system';
import {
@@ -56,7 +54,6 @@ import {
ModalContent,
ModalHeader,
} from '../../component-library';
-import { TextFieldSearch } from '../../component-library/text-field-search/deprecated';
import { ADD_POPULAR_CUSTOM_NETWORK } from '../../../helpers/constants/routes';
import { getEnvironmentType } from '../../../../app/scripts/lib/util';
import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../shared/constants/app';
@@ -71,6 +68,7 @@ import {
} from '../../../ducks/metamask/metamask';
import { getLocalNetworkMenuRedesignFeatureFlag } from '../../../helpers/utils/feature-flags';
import PopularNetworkList from './popular-network-list/popular-network-list';
+import NetworkListSearch from './network-list-search/network-list-search';
export const NetworkListMenu = ({ onClose }) => {
const t = useI18nContext();
@@ -101,8 +99,6 @@ export const NetworkListMenu = ({ onClose }) => {
const isUnlocked = useSelector(getIsUnlocked);
- const showSearch = nonTestNetworks.length > 3;
-
const orderedNetworksList = useSelector(getOrderedNetworksList);
const networkConfigurationChainIds = Object.values(networkConfigurations).map(
@@ -116,6 +112,7 @@ export const NetworkListMenu = ({ onClose }) => {
const notExistingNetworkConfigurations = sortedFeaturedNetworks.filter(
({ chainId }) => !networkConfigurationChainIds.includes(chainId),
);
+
const newOrderNetworks = () => {
if (!orderedNetworksList || orderedNetworksList.length === 0) {
return nonTestNetworks;
@@ -147,6 +144,7 @@ export const NetworkListMenu = ({ onClose }) => {
}, [dispatch, currentlyOnTestNetwork]);
const [searchQuery, setSearchQuery] = useState('');
+ const [focusSearch, setFocusSearch] = useState(false);
const onboardedInThisUISession = useSelector(getOnboardedInThisUISession);
const showNetworkBanner = useSelector(getShowNetworkBanner);
const showBanner =
@@ -175,14 +173,14 @@ export const NetworkListMenu = ({ onClose }) => {
let searchResults =
[...networksList].length === items.length ? items : [...networksList];
- const searchAddNetworkResults =
+ let searchAddNetworkResults =
[...notExistingNetworkConfigurations].length === items.length
? items
: [...notExistingNetworkConfigurations];
- const isSearching = searchQuery !== '';
+ let searchTestNetworkResults = [...testNetworks];
- if (isSearching) {
+ if (focusSearch && searchQuery !== '') {
const fuse = new Fuse(searchResults, {
threshold: 0.2,
location: 0,
@@ -192,12 +190,45 @@ export const NetworkListMenu = ({ onClose }) => {
shouldSort: true,
keys: ['nickname', 'chainId', 'ticker'],
});
+ const fuseForPopularNetworks = new Fuse(searchAddNetworkResults, {
+ threshold: 0.2,
+ location: 0,
+ distance: 100,
+ maxPatternLength: 32,
+ minMatchCharLength: 1,
+ shouldSort: true,
+ keys: ['nickname', 'chainId', 'ticker'],
+ });
+
+ const fuseForTestsNetworks = new Fuse(searchTestNetworkResults, {
+ threshold: 0.2,
+ location: 0,
+ distance: 100,
+ maxPatternLength: 32,
+ minMatchCharLength: 1,
+ shouldSort: true,
+ keys: ['nickname', 'chainId', 'ticker'],
+ });
+
fuse.setCollection(searchResults);
+ fuseForPopularNetworks.setCollection(searchAddNetworkResults);
+ fuseForTestsNetworks.setCollection(searchTestNetworkResults);
+
const fuseResults = fuse.search(searchQuery);
- // Ensure order integrity with original list
+ const fuseForPopularNetworksResults =
+ fuseForPopularNetworks.search(searchQuery);
+ const fuseForTestsNetworksResults =
+ fuseForTestsNetworks.search(searchQuery);
+
searchResults = searchResults.filter((network) =>
fuseResults.includes(network),
);
+ searchAddNetworkResults = searchAddNetworkResults.filter((network) =>
+ fuseForPopularNetworksResults.includes(network),
+ );
+ searchTestNetworkResults = searchTestNetworkResults.filter((network) =>
+ fuseForTestsNetworksResults.includes(network),
+ );
}
const generateNetworkListItem = ({
@@ -210,8 +241,8 @@ export const NetworkListMenu = ({ onClose }) => {
name={network.nickname}
iconSrc={network?.rpcPrefs?.imageUrl}
key={network.id}
- selected={isCurrentNetwork}
- focus={isCurrentNetwork && !showSearch}
+ selected={isCurrentNetwork && !focusSearch}
+ focus={isCurrentNetwork && !focusSearch}
onClick={() => {
dispatch(toggleNetworkMenu());
if (network.providerType) {
@@ -305,27 +336,11 @@ export const NetworkListMenu = ({ onClose }) => {
{t('networkMenuHeading')}
<>
- {showSearch ? (
-
- setSearchQuery(e.target.value)}
- clearButtonOnClick={() => setSearchQuery('')}
- clearButtonProps={{
- size: Size.SM,
- }}
- inputProps={{ autoFocus: true }}
- />
-
- ) : null}
+
{showBanner ? (
{
/>
) : null}
- {searchResults.length === 0 && isSearching ? (
+ {searchResults.length === 0 && focusSearch ? (
{
{showTestNetworks || currentlyOnTestNetwork ? (
- {generateMenuItems(testNetworks)}
+ {generateMenuItems(searchTestNetworkResults)}
) : null}
-
+
{
mockNetworkMenuRedesignToggle.mockReturnValue(false);
});
+ it('renders properly', () => {
+ const { container } = render();
+ expect(container).toMatchSnapshot();
+ });
it('displays important controls', () => {
const { getByText, getByPlaceholderText } = render();
@@ -129,6 +133,7 @@ describe('NetworkListMenu', () => {
expect(queryByText('Chain 5')).toBeInTheDocument();
const searchBox = getByPlaceholderText('Search');
+ fireEvent.focus(searchBox);
fireEvent.change(searchBox, { target: { value: 'Main' } });
expect(queryByText('Chain 5')).not.toBeInTheDocument();
@@ -150,4 +155,51 @@ describe('NetworkListMenu', () => {
document.querySelectorAll('multichain-network-list-item__delete'),
).toHaveLength(0);
});
+
+ describe('NetworkListMenu with ENABLE_NETWORK_UI_REDESIGN', () => {
+ // Set the environment variable before tests run
+ beforeEach(() => {
+ process.env.ENABLE_NETWORK_UI_REDESIGN = 'true';
+ });
+
+ // Reset the environment variable after tests complete
+ afterEach(() => {
+ delete process.env.ENABLE_NETWORK_UI_REDESIGN;
+ });
+
+ it('should display "Arbitrum" when ENABLE_NETWORK_UI_REDESIGN is true', async () => {
+ const { queryByText, getByPlaceholderText } = render();
+
+ // Now "Arbitrum" should be in the document if PopularNetworkList is rendered
+ expect(queryByText('Arbitrum One')).toBeInTheDocument();
+
+ // Simulate typing "Optimism" into the search box
+ const searchBox = getByPlaceholderText('Search');
+ fireEvent.focus(searchBox);
+ fireEvent.change(searchBox, { target: { value: 'OP Mainnet' } });
+
+ // "Optimism" should be visible, but "Arbitrum" should not
+ expect(queryByText('OP Mainnet')).toBeInTheDocument();
+ expect(queryByText('Arbitrum One')).not.toBeInTheDocument();
+ });
+
+ it('should filter testNets when ENABLE_NETWORK_UI_REDESIGN is true', async () => {
+ const { queryByText, getByPlaceholderText } = render({
+ showTestNetworks: true,
+ });
+
+ // Check if all testNets are available
+ expect(queryByText('Linea Sepolia')).toBeInTheDocument();
+ expect(queryByText('Sepolia')).toBeInTheDocument();
+
+ // Simulate typing "Linea Sepolia" into the search box
+ const searchBox = getByPlaceholderText('Search');
+ fireEvent.focus(searchBox);
+ fireEvent.change(searchBox, { target: { value: 'Linea Sepolia' } });
+
+ // "Linea Sepolia" should be visible, but "Sepolia" should not
+ expect(queryByText('Linea Sepolia')).toBeInTheDocument();
+ expect(queryByText('Sepolia')).not.toBeInTheDocument();
+ });
+ });
});
diff --git a/ui/components/multichain/network-list-menu/network-list-search/__snapshots__/network-list-search.test.tsx.snap b/ui/components/multichain/network-list-menu/network-list-search/__snapshots__/network-list-search.test.tsx.snap
new file mode 100644
index 000000000000..6b2597430661
--- /dev/null
+++ b/ui/components/multichain/network-list-menu/network-list-search/__snapshots__/network-list-search.test.tsx.snap
@@ -0,0 +1,28 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`NetworkListSearch renders search list component 1`] = `
+
+`;
diff --git a/ui/components/multichain/network-list-menu/network-list-search/network-list-search.test.tsx b/ui/components/multichain/network-list-menu/network-list-search/network-list-search.test.tsx
new file mode 100644
index 000000000000..c6b164976563
--- /dev/null
+++ b/ui/components/multichain/network-list-menu/network-list-search/network-list-search.test.tsx
@@ -0,0 +1,61 @@
+import React from 'react';
+import { render, fireEvent } from '@testing-library/react';
+import { useI18nContext } from '../../../../hooks/useI18nContext';
+import NetworkListSearch from './network-list-search';
+
+jest.mock('../../../../hooks/useI18nContext', () => ({
+ useI18nContext: jest.fn(),
+}));
+
+describe('NetworkListSearch', () => {
+ const mockSetSearchQuery = jest.fn();
+ const mockSetFocusSearch = jest.fn();
+ const useI18nContextMock = useI18nContext as jest.Mock;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ useI18nContextMock.mockReturnValue((key: string) => key);
+ });
+
+ it('renders search list component', () => {
+ const { container } = render(
+ ,
+ );
+
+ expect(container).toMatchSnapshot();
+ });
+
+ it('should update search query on user input', () => {
+ const { getByPlaceholderText } = render(
+ ,
+ );
+
+ const searchInput = getByPlaceholderText('search');
+ fireEvent.change(searchInput, { target: { value: 'Ethereum' } });
+
+ expect(mockSetSearchQuery).toHaveBeenCalledWith('Ethereum');
+ });
+
+ it('should clear search query when clear button is clicked', () => {
+ const { getByRole } = render(
+ ,
+ );
+
+ const clearButton = getByRole('button', { name: /clear/u });
+ fireEvent.click(clearButton);
+
+ expect(mockSetSearchQuery).toHaveBeenCalledWith('');
+ });
+});
diff --git a/ui/components/multichain/network-list-menu/network-list-search/network-list-search.tsx b/ui/components/multichain/network-list-menu/network-list-search/network-list-search.tsx
new file mode 100644
index 000000000000..fdd74ec8a444
--- /dev/null
+++ b/ui/components/multichain/network-list-menu/network-list-search/network-list-search.tsx
@@ -0,0 +1,44 @@
+import React from 'react';
+import { useI18nContext } from '../../../../hooks/useI18nContext';
+import {
+ Box,
+ ButtonIconSize,
+ TextFieldSearch,
+ TextFieldSearchSize,
+} from '../../../component-library';
+import { BlockSize } from '../../../../helpers/constants/design-system';
+
+const NetworkListSearch = ({
+ searchQuery,
+ setSearchQuery,
+ setFocusSearch,
+}: {
+ searchQuery: string;
+ setSearchQuery: (query: string) => void;
+ setFocusSearch: (val: boolean) => void;
+}) => {
+ const t = useI18nContext();
+
+ return (
+
+ setFocusSearch(true)}
+ onBlur={() => setFocusSearch(false)}
+ onChange={(e) => setSearchQuery(e.target.value)}
+ clearButtonOnClick={() => setSearchQuery('')}
+ clearButtonProps={{
+ size: ButtonIconSize.Sm,
+ }}
+ inputProps={{ 'data-testid': 'network-redesign-modal-search-input' }}
+ data-testid="search-list"
+ />
+
+ );
+};
+
+export default NetworkListSearch;