Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new components for network UI #27085

Merged
merged 11 commits into from
Sep 12, 2024
Prev Previous commit
Next Next commit
add select-rpc-url-modall and rpc-list-item
  • Loading branch information
bergeron committed Sep 11, 2024
commit 15e99555c6695bb6ac11ff6134437573651fb82f
98 changes: 98 additions & 0 deletions ui/components/multichain/network-list-menu/rpc-list-item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React from 'react';
import { infuraProjectId } from '../../../../shared/constants/network';
import { Box, Text } from '../../component-library';
import {
Display,
FlexDirection,
BorderStyle,
BorderColor,
TextColor,
TextVariant,
BackgroundColor,
BlockSize,
} from '../../../helpers/constants/design-system';

// TODO: Use version from network controller with v21 upgrade
enum RpcEndpointType {
Custom = 'custom',
Infura = 'infura',
}

export const stripKeyFromInfuraUrl = (endpoint: string) => {
let modifiedEndpoint = endpoint;

if (modifiedEndpoint.endsWith('/v3/{infuraProjectId}')) {
modifiedEndpoint = modifiedEndpoint.replace('/v3/{infuraProjectId}', '');
} else if (modifiedEndpoint.endsWith(`/v3/${infuraProjectId}`)) {
modifiedEndpoint = modifiedEndpoint.replace(`/v3/${infuraProjectId}`, '');
}

return modifiedEndpoint;
};

export const stripProtocol = (endpoint: string) => {
const url = new URL(endpoint);
return `${url.host}${url.pathname === '/' ? '' : url.pathname}`;
};

// This components represents an RPC endpoint in a list,
// currently when selecting or editing endpoints for a network.
const RpcListItem = ({
rpcEndpoint,
}: {
rpcEndpoint: {
name?: string;
url: string;
type: RpcEndpointType;
};
}) => {
const { url, type } = rpcEndpoint;
const name = type === RpcEndpointType.Infura ? 'Infura' : rpcEndpoint.name;

const displayEndpoint = (endpoint?: string) =>
endpoint ? stripProtocol(stripKeyFromInfuraUrl(endpoint)) : '\u00A0';

const padding = name ? 2 : 4;

return (
<Box
className="rpc-list-item"
display={Display.Flex}
flexDirection={FlexDirection.Column}
paddingTop={padding}
paddingBottom={padding}
{...(!name && {
borderWidth: 2,
borderStyle: BorderStyle.solid,
borderColor: BorderColor.transparent,
})}
>
<Box>
<Text
as="button"
padding={0}
width={BlockSize.Full}
color={name ? TextColor.textDefault : TextColor.textAlternative}
variant={name ? TextVariant.bodyMdMedium : TextVariant.bodySm}
backgroundColor={BackgroundColor.transparent}
ellipsis
>
{name || displayEndpoint(url)}
</Text>
</Box>
{name && (
<Box>
<Text
color={TextColor.textAlternative}
variant={TextVariant.bodySm}
ellipsis
>
{displayEndpoint(url)}
</Text>
</Box>
)}
</Box>
);
};

export default RpcListItem;
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`SelectRpcUrlModal Component renders select rpc url 1`] = `
<div>
<div
class="mm-box"
>
<div
class="mm-box mm-box--display-flex"
>
<div
class="mm-box mm-box--margin-auto mm-box--padding-top-1 mm-box--padding-bottom-8 mm-box--display-flex mm-box--align-items-center"
>
<div
class="mm-box mm-text mm-avatar-base mm-avatar-base--size-sm mm-avatar-network mm-text--body-sm mm-text--text-transform-uppercase mm-box--margin-right-1 mm-box--display-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-text-default mm-box--background-color-background-alternative mm-box--rounded-full mm-box--border-color-transparent box--border-style-solid box--border-width-1"
>
<img
alt="Ethereum Mainnet logo"
class="mm-avatar-network__network-image"
src="./images/eth_logo.svg"
/>
</div>
<p
class="mm-box mm-text mm-text--body-sm mm-box--color-text-alternative"
>
Ethereum Mainnet
</p>
</div>
</div>
<div
class="mm-box networks-tab__item networks-tab__item--selected mm-box--padding-right-4 mm-box--padding-left-4 mm-box--display-flex mm-box--align-items-center"
>
<div
class="mm-box networks-tab__item-selected-pill mm-box--background-color-primary-default mm-box--rounded-pill"
/>
<div
class="mm-box rpc-list-item mm-box--padding-top-4 mm-box--padding-bottom-4 mm-box--display-flex mm-box--flex-direction-column mm-box--border-style-solid mm-box--border-color-transparent mm-box--border-width-2"
>
<div
class="mm-box"
>
<button
class="mm-box mm-text mm-text--body-sm mm-text--ellipsis mm-box--padding-0 mm-box--width-full mm-box--color-text-alternative mm-box--background-color-transparent"
>
mainnet.infura.io/v3/
</button>
</div>
</div>
</div>
<div
class="mm-box networks-tab__item mm-box--padding-right-4 mm-box--padding-left-4 mm-box--display-flex mm-box--align-items-center"
>
<div
class="mm-box rpc-list-item mm-box--padding-top-4 mm-box--padding-bottom-4 mm-box--display-flex mm-box--flex-direction-column mm-box--border-style-solid mm-box--border-color-transparent mm-box--border-width-2"
>
<div
class="mm-box"
>
<button
class="mm-box mm-text mm-text--body-sm mm-text--ellipsis mm-box--padding-0 mm-box--width-full mm-box--color-text-alternative mm-box--background-color-transparent"
>
rpc.flashbots.net
</button>
</div>
</div>
</div>
</div>
</div>
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import React from 'react';
import { fireEvent, screen } from '@testing-library/react';
import configureMockStore from 'redux-mock-store';
import { renderWithProvider } from '../../../../../test/lib/render-helpers';
import {
// TODO: Add this API with network controller v21 upgrade
// updateNetwork,
setActiveNetwork,
setEditedNetwork,
toggleNetworkMenu,
} from '../../../../store/actions';
import { CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP } from '../../../../../shared/constants/network';
import { stripProtocol } from '../rpc-list-item';
import { SelectRpcUrlModal } from './select-rpc-url-modal'; // Adjust the path as needed

const mockDispatch = jest.fn();
jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
useDispatch: () => mockDispatch,
}));

jest.mock('../../../../store/actions', () => ({
// TODO: Add this API with network controller v21 upgrade
// updateNetwork: jest.fn(),
setActiveNetwork: jest.fn(),
setEditedNetwork: jest.fn(),
toggleNetworkMenu: jest.fn(),
}));

const mockStore = configureMockStore();
const networkConfiguration = {
chainId: '0x1',
name: 'Ethereum Mainnet',
rpcEndpoints: [
{ url: 'https://mainnet.infura.io/v3/', networkClientId: 'mainnet' },
{ url: 'https://rpc.flashbots.net/', networkClientId: 'flashbots' },
],
defaultRpcEndpointIndex: 0,
};

const store = mockStore({
metamask: {
networks: [networkConfiguration],
activeNetwork: '0x1',
},
});

describe('SelectRpcUrlModal Component', () => {
beforeEach(() => {
mockDispatch.mockClear();
});

it('renders select rpc url', () => {
const { container } = renderWithProvider(
<SelectRpcUrlModal networkConfiguration={networkConfiguration} />,
store,
);
expect(container).toMatchSnapshot();
});

it('should render the component correctly with network image and name', () => {
const { getByRole, getByText } = renderWithProvider(
<SelectRpcUrlModal networkConfiguration={networkConfiguration} />,
store,
);

const imageSrc =
CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP[
networkConfiguration.chainId as keyof typeof CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP
];

const networkImage = getByRole('img');

expect(networkImage).toBeInTheDocument();
expect(networkImage).toHaveAttribute('src', imageSrc);
expect(getByText(networkConfiguration.name)).toBeInTheDocument();
});

it('should render all RPC endpoints and highlight the selected one', () => {
const { getByText } = renderWithProvider(
<SelectRpcUrlModal networkConfiguration={networkConfiguration} />,
store,
);

networkConfiguration.rpcEndpoints.forEach((rpcEndpoint) => {
expect(getByText(stripProtocol(rpcEndpoint.url))).toBeInTheDocument();
});

const selectedItem = getByText(
stripProtocol(networkConfiguration.rpcEndpoints[0].url),
).closest('.networks-tab__item');

expect(selectedItem).toHaveClass('networks-tab__item--selected');
});

it('should dispatch the correct actions when an RPC endpoint is clicked', () => {
const { getByText } = renderWithProvider(
<SelectRpcUrlModal networkConfiguration={networkConfiguration} />,
store,
);

const rpcEndpoint = getByText(
stripProtocol(networkConfiguration.rpcEndpoints[1].url),
);
fireEvent.click(rpcEndpoint);

// TODO: Add this API with network controller v21 upgrade
// expect(mockDispatch).toHaveBeenCalledWith(
// updateNetwork({
// ...networkConfiguration,
// defaultRpcEndpointIndex: 1,
// }),
// );
expect(mockDispatch).toHaveBeenCalledWith(setActiveNetwork('flashbots'));
expect(mockDispatch).toHaveBeenCalledWith(setEditedNetwork());
expect(mockDispatch).toHaveBeenCalledWith(toggleNetworkMenu());
});

it('should render the selected indicator correctly for the default RPC', () => {
const { container } = renderWithProvider(
<SelectRpcUrlModal networkConfiguration={networkConfiguration} />,
store,
);

const selectedPill = container.querySelector(
'.networks-tab__item-selected-pill',
);
expect(selectedPill).toBeInTheDocument();
});

it('should render the modal with a network image', () => {
renderWithProvider(
<SelectRpcUrlModal networkConfiguration={networkConfiguration} />,
store,
);

const networkImage = screen.getByRole('img');
expect(networkImage).toBeInTheDocument();
expect(networkImage).toHaveAttribute(
'src',
CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP[
networkConfiguration.chainId as keyof typeof CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP
],
);
});

it('should handle click on RPC URL and update the network', () => {
renderWithProvider(
<SelectRpcUrlModal networkConfiguration={networkConfiguration} />,
store,
);

fireEvent.click(
screen.getByText(stripProtocol(networkConfiguration.rpcEndpoints[1].url)),
);

// TODO: Add this API with network controller v21 upgrade
// expect(mockDispatch).toHaveBeenCalledWith(
// updateNetwork({
// ...networkConfiguration,
// defaultRpcEndpointIndex: 1,
// }),
// );
expect(mockDispatch).toHaveBeenCalledWith(
setActiveNetwork(networkConfiguration.rpcEndpoints[1].networkClientId),
);
expect(mockDispatch).toHaveBeenCalledWith(setEditedNetwork());
expect(mockDispatch).toHaveBeenCalledWith(toggleNetworkMenu());
});
});
Loading