Skip to content

Commit

Permalink
feat: add search feature
Browse files Browse the repository at this point in the history
  • Loading branch information
salimtb committed Jun 12, 2024
1 parent 92e6b94 commit 354affc
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`NetworkListMenu renders properly 1`] = `<div />`;
56 changes: 26 additions & 30 deletions ui/components/multichain/network-list-menu/network-list-menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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';
Expand All @@ -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();
Expand Down Expand Up @@ -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(
Expand All @@ -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;
Expand Down Expand Up @@ -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];
Expand All @@ -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 = ({
Expand All @@ -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) {
Expand Down Expand Up @@ -305,27 +318,10 @@ export const NetworkListMenu = ({ onClose }) => {
{t('networkMenuHeading')}
</ModalHeader>
<>
{showSearch ? (
<Box
paddingLeft={4}
paddingRight={4}
paddingBottom={4}
paddingTop={0}
>
<TextFieldSearch
size={Size.SM}
width={BlockSize.Full}
placeholder={t('search')}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
clearButtonOnClick={() => setSearchQuery('')}
clearButtonProps={{
size: Size.SM,
}}
inputProps={{ autoFocus: true }}
/>
</Box>
) : null}
<NetworkListSearch
searchQuery={searchQuery}
setSearchQuery={setSearchQuery}
/>
{showBanner ? (
<BannerBase
className="network-list-menu__banner"
Expand Down Expand Up @@ -428,7 +424,7 @@ export const NetworkListMenu = ({ onClose }) => {
</Box>
) : null}
</Box>
<Box padding={4}>
<Box paddingLeft={4} paddingRight={4} paddingTop={4}>
<ButtonSecondary
size={ButtonSecondarySize.Lg}
startIconName={IconName.Add}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ describe('NetworkListMenu', () => {
mockNetworkMenuRedesignToggle.mockReturnValue(false);
});

it('renders properly', () => {
const { container } = render();
expect(container).toMatchSnapshot();
});
it('displays important controls', () => {
const { getByText, getByPlaceholderText } = render();

Expand Down Expand Up @@ -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();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`NetworkListSearch renders search list component 1`] = `
<div>
<div
class="mm-box mm-box--padding-top-0 mm-box--padding-right-4 mm-box--padding-bottom-4 mm-box--padding-left-4"
>
<div
class="mm-box mm-text-field mm-text-field--size-lg mm-text-field--focused mm-text-field--truncate mm-text-field-search mm-box--padding-right-0 mm-box--padding-left-4 mm-box--display-inline-flex mm-box--align-items-center mm-box--width-full mm-box--background-color-background-default mm-box--rounded-sm mm-box--border-width-1 box--border-style-solid"
data-testid="search-list"
>
<span
class="mm-box mm-icon mm-icon--size-sm mm-box--display-inline-block mm-box--color-inherit"
style="mask-image: url('./images/icons/search.svg');"
/>
<input
autocomplete="off"
class="mm-box mm-text mm-input mm-input--disable-state-styles mm-text-field__input mm-text--body-md mm-box--margin-0 mm-box--margin-right-6 mm-box--padding-0 mm-box--padding-right-4 mm-box--padding-left-2 mm-box--color-text-default mm-box--background-color-transparent mm-box--border-style-none"
data-testid="network-redesign-modal-search-input"
focused="true"
placeholder="search"
type="search"
value=""
/>
</div>
</div>
</div>
`;
Original file line number Diff line number Diff line change
@@ -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(
<NetworkListSearch searchQuery="" setSearchQuery={mockSetSearchQuery} />,
);

expect(container).toMatchSnapshot();
});

it('should update search query on user input', () => {
const { getByPlaceholderText } = render(
<NetworkListSearch searchQuery="" setSearchQuery={mockSetSearchQuery} />,
);

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(
<NetworkListSearch
searchQuery="Ethereum"
setSearchQuery={mockSetSearchQuery}
/>,
);

const clearButton = getByRole('button', { name: /clear/u });
fireEvent.click(clearButton);

expect(mockSetSearchQuery).toHaveBeenCalledWith('');
});
});
Original file line number Diff line number Diff line change
@@ -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 (
<Box paddingLeft={4} paddingRight={4} paddingBottom={4} paddingTop={0}>
<TextFieldSearch
size={TextFieldSearchSize.Lg}
width={BlockSize.Full}
placeholder={t('search')}
autoFocus
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
clearButtonOnClick={() => setSearchQuery('')}
clearButtonProps={{
size: ButtonIconSize.Sm,
}}
inputProps={{ 'data-testid': 'network-redesign-modal-search-input' }}
data-testid="search-list"
/>
</Box>
);
};

export default NetworkListSearch;

0 comments on commit 354affc

Please sign in to comment.