Redesign screen/page "List of networks" (#13560)
Co-authored-by: George Marshall <george.marshall@consensys.net> Co-authored-by: Elliot Winkler <elliot.winkler@gmail.com>feature/default_network_editable
parent
8174aaa6b5
commit
8184b150cb
@ -0,0 +1,114 @@ |
|||||||
|
import React, { useState, useContext } from 'react'; |
||||||
|
import PropTypes from 'prop-types'; |
||||||
|
import Fuse from 'fuse.js'; |
||||||
|
import InputAdornment from '@material-ui/core/InputAdornment'; |
||||||
|
import TextField from '../../../../components/ui/text-field'; |
||||||
|
import { I18nContext } from '../../../../contexts/i18n'; |
||||||
|
import SearchIcon from '../../../../components/ui/search-icon'; |
||||||
|
|
||||||
|
export default function CustomContentSearch({ |
||||||
|
onSearch, |
||||||
|
error, |
||||||
|
networksList, |
||||||
|
searchQueryInput, |
||||||
|
}) { |
||||||
|
const t = useContext(I18nContext); |
||||||
|
const [searchIconColor, setSearchIconColor] = useState( |
||||||
|
'var(--color-icon-muted)', |
||||||
|
); |
||||||
|
|
||||||
|
const networksListArray = Object.values(networksList); |
||||||
|
const networksSearchFuse = new Fuse(networksListArray, { |
||||||
|
shouldSort: true, |
||||||
|
threshold: 0.2, |
||||||
|
location: 0, |
||||||
|
distance: 100, |
||||||
|
maxPatternLength: 32, |
||||||
|
minMatchCharLength: 1, |
||||||
|
keys: ['label', 'labelKey'], |
||||||
|
}); |
||||||
|
|
||||||
|
const handleSearch = async (searchQuery) => { |
||||||
|
if (searchQuery === '') { |
||||||
|
setSearchIconColor('var(--color-icon-muted)'); |
||||||
|
} else { |
||||||
|
setSearchIconColor('var(--color-icon-default)'); |
||||||
|
} |
||||||
|
|
||||||
|
const fuseSearchResult = networksSearchFuse.search(searchQuery); |
||||||
|
const results = searchQuery ? [...fuseSearchResult] : networksListArray; |
||||||
|
await onSearch({ searchQuery, results }); |
||||||
|
}; |
||||||
|
|
||||||
|
const renderStartAdornment = () => { |
||||||
|
return ( |
||||||
|
<InputAdornment position="start"> |
||||||
|
<SearchIcon color={searchIconColor} /> |
||||||
|
</InputAdornment> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
const renderEndAdornment = () => { |
||||||
|
return ( |
||||||
|
<> |
||||||
|
{searchQueryInput && ( |
||||||
|
<InputAdornment |
||||||
|
className="imageclosectn" |
||||||
|
position="end" |
||||||
|
onClick={() => handleSearch('')} |
||||||
|
> |
||||||
|
<i |
||||||
|
className="fa fa-times networks-tab__imageclose" |
||||||
|
width="17" |
||||||
|
heigth="17" |
||||||
|
title="Close" |
||||||
|
/> |
||||||
|
</InputAdornment> |
||||||
|
)} |
||||||
|
</> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<TextField |
||||||
|
id="search-networks" |
||||||
|
data-testid="search-networks" |
||||||
|
placeholder={t('customContentSearch')} |
||||||
|
type="text" |
||||||
|
value={searchQueryInput} |
||||||
|
onChange={(e) => handleSearch(e.target.value)} |
||||||
|
error={error} |
||||||
|
fullWidth |
||||||
|
autoFocus |
||||||
|
autoComplete="off" |
||||||
|
classes={{ |
||||||
|
inputRoot: 'networks-tab__networks-list__custom-search-network', |
||||||
|
}} |
||||||
|
startAdornment={renderStartAdornment()} |
||||||
|
endAdornment={renderEndAdornment()} |
||||||
|
/> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
CustomContentSearch.propTypes = { |
||||||
|
/** |
||||||
|
* The function searches the list of networks depending on |
||||||
|
* the entered parameter and returns the entire list of |
||||||
|
* networks when the user clicks on 'X' on the search tab |
||||||
|
*/ |
||||||
|
onSearch: PropTypes.func, |
||||||
|
/** |
||||||
|
* An error message is displayed when a user searches for a specific |
||||||
|
* network on the search tab and that network does not exist |
||||||
|
* in the networks list |
||||||
|
*/ |
||||||
|
error: PropTypes.string, |
||||||
|
/** |
||||||
|
* The list of networks available for search. |
||||||
|
*/ |
||||||
|
networksList: PropTypes.array, |
||||||
|
/** |
||||||
|
* Search for a specific network(s) by label or labelKey |
||||||
|
*/ |
||||||
|
searchQueryInput: PropTypes.string, |
||||||
|
}; |
@ -0,0 +1,23 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import testData from '../../../../../.storybook/test-data'; |
||||||
|
import CustomContentSearch from './custom-content-search'; |
||||||
|
|
||||||
|
export default { |
||||||
|
title: 'Pages/Settings/NetworksTab/CustomContentSearch', |
||||||
|
id: __filename, |
||||||
|
argTypes: { |
||||||
|
error: { |
||||||
|
control: 'text', |
||||||
|
}, |
||||||
|
searchQueryInput: { |
||||||
|
control: 'text', |
||||||
|
}, |
||||||
|
onSearch: { |
||||||
|
action: 'onSearch', |
||||||
|
}, |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
export const CustomContentSearchComponent = (args) => { |
||||||
|
return <CustomContentSearch {...args} networksList={testData.networkList} />; |
||||||
|
}; |
@ -0,0 +1,110 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { fireEvent, render, screen } from '@testing-library/react'; |
||||||
|
import Fuse from 'fuse.js'; |
||||||
|
import configureStore from '../../../../store/store'; |
||||||
|
import { renderWithProvider } from '../../../../../test/lib/render-helpers'; |
||||||
|
import testData from '../../../../../.storybook/test-data'; |
||||||
|
import CustomContentSearch from './custom-content-search'; |
||||||
|
|
||||||
|
function renderComponent({ componentProps = {} } = {}) { |
||||||
|
const store = configureStore({}); |
||||||
|
return renderWithProvider(<CustomContentSearch {...componentProps} />, store); |
||||||
|
} |
||||||
|
|
||||||
|
describe('CustomContentSearch', () => { |
||||||
|
it('should render custom content search correctly', () => { |
||||||
|
const onSearch = jest.fn(); |
||||||
|
const wrapper = renderComponent({ |
||||||
|
componentProps: { onSearch, networksList: testData.networkList }, |
||||||
|
}); |
||||||
|
expect(wrapper.getByTestId('search-networks')).toBeDefined(); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should check placeholder text in TextField input', () => { |
||||||
|
const onSearch = jest.fn(); |
||||||
|
const wrapper = renderComponent({ |
||||||
|
componentProps: { onSearch, networksList: testData.networkList }, |
||||||
|
}); |
||||||
|
const { getByPlaceholderText } = wrapper; |
||||||
|
expect( |
||||||
|
getByPlaceholderText('Search for a previously added network'), |
||||||
|
).toBeInTheDocument(); |
||||||
|
}); |
||||||
|
|
||||||
|
it('re-render the same component with different props', () => { |
||||||
|
const onSearch = jest.fn(); |
||||||
|
const { rerender } = render( |
||||||
|
<CustomContentSearch |
||||||
|
onSearch={onSearch} |
||||||
|
networksList={[]} |
||||||
|
searchQueryInput="" |
||||||
|
/>, |
||||||
|
); |
||||||
|
const input = screen.getByTestId('search-networks'); |
||||||
|
expect(input.value).toBe(''); |
||||||
|
rerender( |
||||||
|
<CustomContentSearch |
||||||
|
onSearch={onSearch} |
||||||
|
networksList={[]} |
||||||
|
searchQueryInput="Polygon" |
||||||
|
/>, |
||||||
|
); |
||||||
|
expect(input.value).toBe('Polygon'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should call onSearch prop with input value', () => { |
||||||
|
const onSearch = jest.fn(); |
||||||
|
const wrapper = renderComponent({ |
||||||
|
componentProps: { |
||||||
|
onSearch, |
||||||
|
networksList: [], |
||||||
|
searchQueryInput: 'Avalanche', |
||||||
|
}, |
||||||
|
}); |
||||||
|
const input = wrapper.getByTestId('search-networks'); |
||||||
|
fireEvent.change(input, { target: { value: 'Polygon' } }); |
||||||
|
expect(input.value).toBe('Avalanche'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should check if error is shown if search does not return any network from the list', () => { |
||||||
|
const onSearch = jest.fn(); |
||||||
|
const networksSearchFuse = new Fuse(testData.networkList, { |
||||||
|
keys: ['label', 'labelKey'], |
||||||
|
}); |
||||||
|
const fuseSearchResult = networksSearchFuse.search('Optimism'); |
||||||
|
const wrapper = renderComponent({ |
||||||
|
componentProps: { |
||||||
|
onSearch, |
||||||
|
networksList: testData.networkList, |
||||||
|
searchQueryInput: 'Optimism', |
||||||
|
error: 'No matching results found.', |
||||||
|
}, |
||||||
|
}); |
||||||
|
const input = wrapper.getByTestId('search-networks'); |
||||||
|
expect(fuseSearchResult).toHaveLength(0); |
||||||
|
fireEvent.change(input, { |
||||||
|
target: { error: 'No matching results found.' }, |
||||||
|
}); |
||||||
|
expect(input.error).toBe('No matching results found.'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should check if error is not shown if search return some network from the list', () => { |
||||||
|
const onSearch = jest.fn(); |
||||||
|
const networksSearchFuse = new Fuse(testData.networkList, { |
||||||
|
keys: ['label', 'labelKey'], |
||||||
|
}); |
||||||
|
const fuseSearchResult = networksSearchFuse.search('ropsten'); |
||||||
|
const wrapper = renderComponent({ |
||||||
|
componentProps: { |
||||||
|
onSearch, |
||||||
|
networksList: testData.networkList, |
||||||
|
searchQueryInput: 'Avalanche', |
||||||
|
error: '', |
||||||
|
}, |
||||||
|
}); |
||||||
|
const input = wrapper.getByTestId('search-networks'); |
||||||
|
expect(fuseSearchResult).toHaveLength(1); |
||||||
|
fireEvent.change(input, { target: { error: '' } }); |
||||||
|
expect(input.error).toBe(''); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './custom-content-search'; |
@ -0,0 +1,28 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import testData from '../../../../../.storybook/test-data'; |
||||||
|
import NetworksList from './networks-list'; |
||||||
|
|
||||||
|
export default { |
||||||
|
title: 'Pages/Settings/NetworksTab/NetworksList', |
||||||
|
id: __filename, |
||||||
|
argTypes: { |
||||||
|
networkDefaultedToProvider: { |
||||||
|
control: 'boolean', |
||||||
|
}, |
||||||
|
networkIsSelected: { |
||||||
|
control: 'boolean', |
||||||
|
}, |
||||||
|
networksToRender: { |
||||||
|
control: 'array', |
||||||
|
}, |
||||||
|
}, |
||||||
|
args: { |
||||||
|
networkDefaultedToProvider: false, |
||||||
|
networkIsSelected: false, |
||||||
|
networksToRender: testData.networkList, |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
export const NetworksListComponent = (args) => { |
||||||
|
return <NetworksList {...args} />; |
||||||
|
}; |
Loading…
Reference in new issue