From 354affc60d3a1b61833e9b4632e5a1273ba6f5b6 Mon Sep 17 00:00:00 2001 From: salimtb Date: Mon, 10 Jun 2024 10:33:40 +0200 Subject: [PATCH] feat: add search feature --- .../network-list-menu.test.js.snap | 3 + .../network-list-menu/network-list-menu.js | 56 +++++++++---------- .../network-list-menu.test.js | 31 ++++++++++ .../network-list-search.test.tsx.snap | 28 ++++++++++ .../network-list-search.test.tsx | 51 +++++++++++++++++ .../network-list-search.tsx | 40 +++++++++++++ 6 files changed, 179 insertions(+), 30 deletions(-) create mode 100644 ui/components/multichain/network-list-menu/__snapshots__/network-list-menu.test.js.snap create mode 100644 ui/components/multichain/network-list-menu/network-list-search/__snapshots__/network-list-search.test.tsx.snap create mode 100644 ui/components/multichain/network-list-menu/network-list-search/network-list-search.test.tsx create mode 100644 ui/components/multichain/network-list-menu/network-list-search/network-list-search.tsx 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..b6ec278e4e93 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; @@ -175,7 +172,7 @@ export const NetworkListMenu = ({ onClose }) => { let searchResults = [...networksList].length === items.length ? items : [...networksList]; - const searchAddNetworkResults = + let searchAddNetworkResults = [...notExistingNetworkConfigurations].length === items.length ? items : [...notExistingNetworkConfigurations]; @@ -192,12 +189,28 @@ 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'], + }); + fuse.setCollection(searchResults); + fuseForPopularNetworks.setCollection(searchAddNetworkResults); const fuseResults = fuse.search(searchQuery); - // Ensure order integrity with original list + const fuseForPopularNetworksResults = + fuseForPopularNetworks.search(searchQuery); + searchResults = searchResults.filter((network) => fuseResults.includes(network), ); + searchAddNetworkResults = searchAddNetworkResults.filter((network) => + fuseForPopularNetworksResults.includes(network), + ); } const generateNetworkListItem = ({ @@ -211,7 +224,7 @@ export const NetworkListMenu = ({ onClose }) => { iconSrc={network?.rpcPrefs?.imageUrl} key={network.id} selected={isCurrentNetwork} - focus={isCurrentNetwork && !showSearch} + focus={isCurrentNetwork && !isSearching} onClick={() => { dispatch(toggleNetworkMenu()); if (network.providerType) { @@ -305,27 +318,10 @@ export const NetworkListMenu = ({ onClose }) => { {t('networkMenuHeading')} <> - {showSearch ? ( - - setSearchQuery(e.target.value)} - clearButtonOnClick={() => setSearchQuery('')} - clearButtonProps={{ - size: Size.SM, - }} - inputProps={{ autoFocus: true }} - /> - - ) : null} + {showBanner ? ( { ) : null} - + { mockNetworkMenuRedesignToggle.mockReturnValue(false); }); + it('renders properly', () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); it('displays important controls', () => { const { getByText, getByPlaceholderText } = render(); @@ -150,4 +154,31 @@ 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.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(); + }); + }); }); 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..4e96eca642e2 --- /dev/null +++ b/ui/components/multichain/network-list-menu/network-list-search/network-list-search.test.tsx @@ -0,0 +1,51 @@ +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 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..4efbd6e2a780 --- /dev/null +++ b/ui/components/multichain/network-list-menu/network-list-search/network-list-search.tsx @@ -0,0 +1,40 @@ +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, +}: { + searchQuery: string; + setSearchQuery: (query: string) => void; +}) => { + const t = useI18nContext(); + + return ( + + 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;