diff --git a/ui/components/app/asset-list/asset-list.js b/ui/components/app/asset-list/asset-list.js index 427e60690984..e83606e8bfe7 100644 --- a/ui/components/app/asset-list/asset-list.js +++ b/ui/components/app/asset-list/asset-list.js @@ -12,21 +12,26 @@ import { getNativeCurrencyImage, getDetectedTokensInCurrentNetwork, getIstokenDetectionInactiveOnNonMainnetSupportedNetwork, + getTokenList, } from '../../../selectors'; import { getNativeCurrency } from '../../../ducks/metamask/metamask'; import { useCurrencyDisplay } from '../../../hooks/useCurrencyDisplay'; -import Typography from '../../ui/typography/typography'; import Box from '../../ui/box/box'; import { Color, - TypographyVariant, - FONT_WEIGHT, - JustifyContent, + TextVariant, + TEXT_ALIGN, } from '../../../helpers/constants/design-system'; import { useI18nContext } from '../../../hooks/useI18nContext'; import { MetaMetricsContext } from '../../../contexts/metametrics'; import { EVENT, EVENT_NAMES } from '../../../../shared/constants/metametrics'; import DetectedToken from '../detected-token/detected-token'; +import { + DetectedTokensBanner, + MultichainTokenListItem, + MultichainImportTokenLink, +} from '../../multichain'; +import { Text } from '../../component-library'; import DetectedTokensLink from './detetcted-tokens-link/detected-tokens-link'; const AssetList = ({ onClickAsset }) => { @@ -69,20 +74,38 @@ const AssetList = ({ onClickAsset }) => { const istokenDetectionInactiveOnNonMainnetSupportedNetwork = useSelector( getIstokenDetectionInactiveOnNonMainnetSupportedNetwork, ); - + const tokenList = useSelector(getTokenList); + const tokenData = Object.values(tokenList).find( + (token) => token.symbol === primaryCurrencyProperties.suffix, + ); + const title = tokenData?.name || primaryCurrencyProperties.suffix; return ( <> - onClickAsset(nativeCurrency)} - data-testid="wallet-balance" - primary={ - primaryCurrencyProperties.value ?? secondaryCurrencyProperties.value - } - tokenSymbol={primaryCurrencyProperties.suffix} - secondary={showFiat ? secondaryCurrencyDisplay : undefined} - tokenImage={balanceIsLoading ? null : primaryTokenImage} - identiconBorder - /> + {process.env.MULTICHAIN ? ( + onClickAsset(nativeCurrency)} + title={title} + primary={ + primaryCurrencyProperties.value ?? secondaryCurrencyProperties.value + } + tokenSymbol={primaryCurrencyProperties.suffix} + secondary={showFiat ? secondaryCurrencyDisplay : undefined} + tokenImage={balanceIsLoading ? null : primaryTokenImage} + /> + ) : ( + onClickAsset(nativeCurrency)} + data-testid="wallet-balance" + primary={ + primaryCurrencyProperties.value ?? secondaryCurrencyProperties.value + } + tokenSymbol={primaryCurrencyProperties.suffix} + secondary={showFiat ? secondaryCurrencyDisplay : undefined} + tokenImage={balanceIsLoading ? null : primaryTokenImage} + identiconBorder + /> + )} + { onClickAsset(tokenAddress); @@ -98,19 +121,36 @@ const AssetList = ({ onClickAsset }) => { /> {detectedTokens.length > 0 && !istokenDetectionInactiveOnNonMainnetSupportedNetwork && ( - + <> + {process.env.MULTICHAIN ? ( + setShowDetectedTokens(true)} + margin={4} + /> + ) : ( + + )} + )} 0 ? 0 : 4}> - - - {t('missingToken')} - - - + {process.env.MULTICHAIN ? ( + + ) : ( + <> + + {t('missingToken')} + + + + + )} {showDetectedTokens && ( diff --git a/ui/components/app/token-cell/token-cell.js b/ui/components/app/token-cell/token-cell.js index 1a18f9c838c7..60d8f304a879 100644 --- a/ui/components/app/token-cell/token-cell.js +++ b/ui/components/app/token-cell/token-cell.js @@ -3,9 +3,12 @@ import PropTypes from 'prop-types'; import React from 'react'; import { useSelector } from 'react-redux'; import AssetListItem from '../asset-list-item'; -import { getSelectedAddress } from '../../../selectors'; +import { getSelectedAddress, getTokenList } from '../../../selectors'; import { useI18nContext } from '../../../hooks/useI18nContext'; import { useTokenFiatAmount } from '../../../hooks/useTokenFiatAmount'; +import { MultichainTokenListItem } from '../../multichain'; +import { ButtonLink, Text } from '../../component-library'; +import { TextColor } from '../../../helpers/constants/design-system'; export default function TokenCell({ address, @@ -19,39 +22,58 @@ export default function TokenCell({ }) { const userAddress = useSelector(getSelectedAddress); const t = useI18nContext(); - + const tokenList = useSelector(getTokenList); + const tokenData = Object.values(tokenList).find( + (token) => token.symbol === symbol, + ); + const title = tokenData?.name || symbol; + const tokenImage = tokenData?.iconUrl || image; const formattedFiat = useTokenFiatAmount(address, string, symbol); const warning = balanceError ? ( - + {t('troubleTokenBalances')} - event.stopPropagation()} - style={{ color: 'var(--color-warning-default)' }} + textProps={{ + color: TextColor.warningDefault, + }} > {t('here')} - - + + ) : null; return ( - + <> + {process.env.MULTICHAIN ? ( + onClick(address)} + tokenSymbol={symbol} + tokenImage={tokenImage} + primary={`${string || 0}`} + secondary={formattedFiat} + title={title} + /> + ) : ( + onClick(address)} + tokenAddress={address} + tokenSymbol={symbol} + tokenDecimals={decimals} + tokenImage={image} + warning={warning} + primary={`${string || 0}`} + secondary={formattedFiat} + isERC721={isERC721} + /> + )} + ); } diff --git a/ui/components/multichain/detected-token-banner/__snapshots__/detected-token-banner.test.js.snap b/ui/components/multichain/detected-token-banner/__snapshots__/detected-token-banner.test.js.snap new file mode 100644 index 000000000000..cbf587cb2064 --- /dev/null +++ b/ui/components/multichain/detected-token-banner/__snapshots__/detected-token-banner.test.js.snap @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DetectedTokensBanner should render correctly 1`] = ` +
+
+ +
+

+ 3 new tokens found in this account +

+ +
+
+
+`; diff --git a/ui/components/multichain/detected-token-banner/detected-token-banner.js b/ui/components/multichain/detected-token-banner/detected-token-banner.js new file mode 100644 index 000000000000..790630ef71d2 --- /dev/null +++ b/ui/components/multichain/detected-token-banner/detected-token-banner.js @@ -0,0 +1,60 @@ +import React, { useContext } from 'react'; +import { useSelector } from 'react-redux'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +import { useI18nContext } from '../../../hooks/useI18nContext'; +import { getDetectedTokensInCurrentNetwork } from '../../../selectors'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; +import { EVENT, EVENT_NAMES } from '../../../../shared/constants/metametrics'; +import { BannerAlert } from '../../component-library'; + +export const DetectedTokensBanner = ({ + className, + actionButtonOnClick, + ...props +}) => { + const t = useI18nContext(); + const trackEvent = useContext(MetaMetricsContext); + + const detectedTokens = useSelector(getDetectedTokensInCurrentNetwork); + const detectedTokensDetails = detectedTokens.map( + ({ address, symbol }) => `${symbol} - ${address}`, + ); + + const handleOnClick = () => { + actionButtonOnClick(); + trackEvent({ + event: EVENT_NAMES.TOKEN_IMPORT_CLICKED, + category: EVENT.CATEGORIES.WALLET, + properties: { + source: EVENT.SOURCE.TOKEN.DETECTED, + tokens: detectedTokensDetails, + }, + }); + }; + return ( + + {detectedTokens.length === 1 + ? t('numberOfNewTokensDetectedSingular') + : t('numberOfNewTokensDetectedPlural', [detectedTokens.length])} + + ); +}; + +DetectedTokensBanner.propTypes = { + /** + * Handler to be passed to the DetectedTokenBanner component + */ + actionButtonOnClick: PropTypes.func.isRequired, + /** + * An additional className to the DetectedTokenBanner component + */ + className: PropTypes.string, +}; diff --git a/ui/components/multichain/detected-token-banner/detected-token-banner.stories.js b/ui/components/multichain/detected-token-banner/detected-token-banner.stories.js new file mode 100644 index 000000000000..080b476effd3 --- /dev/null +++ b/ui/components/multichain/detected-token-banner/detected-token-banner.stories.js @@ -0,0 +1,14 @@ +import React from 'react'; +import { DetectedTokensBanner } from './detected-token-banner'; + +export default { + title: 'Components/Multichain/DetectedTokensBanner', + component: DetectedTokensBanner, + argTypes: { + actionButtonOnClick: { action: 'setShowDetectedTokens' }, + }, +}; + +export const DefaultStory = (args) => ; + +DefaultStory.storyName = 'Default'; diff --git a/ui/components/multichain/detected-token-banner/detected-token-banner.test.js b/ui/components/multichain/detected-token-banner/detected-token-banner.test.js new file mode 100644 index 000000000000..e7c78b47b3b6 --- /dev/null +++ b/ui/components/multichain/detected-token-banner/detected-token-banner.test.js @@ -0,0 +1,39 @@ +import React from 'react'; +import { renderWithProvider, screen, fireEvent } from '../../../../test/jest'; +import configureStore from '../../../store/store'; +import testData from '../../../../.storybook/test-data'; + +import { DetectedTokensBanner } from './detected-token-banner'; + +describe('DetectedTokensBanner', () => { + let setShowDetectedTokensSpy; + + const args = {}; + + beforeEach(() => { + setShowDetectedTokensSpy = jest.fn(); + args.actionButtonOnClick = setShowDetectedTokensSpy; + }); + + it('should render correctly', () => { + const store = configureStore(testData); + const { getByTestId, container } = renderWithProvider( + , + store, + ); + + expect(getByTestId('detected-token-banner')).toBeDefined(); + expect(container).toMatchSnapshot(); + }); + it('should render number of tokens detected link', () => { + const store = configureStore(testData); + renderWithProvider(, store); + + expect( + screen.getByText('3 new tokens found in this account'), + ).toBeInTheDocument(); + + fireEvent.click(screen.getByText('Import tokens')); + expect(setShowDetectedTokensSpy).toHaveBeenCalled(); + }); +}); diff --git a/ui/components/multichain/detected-token-banner/index.js b/ui/components/multichain/detected-token-banner/index.js new file mode 100644 index 000000000000..0fe2b61deaa6 --- /dev/null +++ b/ui/components/multichain/detected-token-banner/index.js @@ -0,0 +1 @@ +export { DetectedTokensBanner } from './detected-token-banner'; diff --git a/ui/components/multichain/index.js b/ui/components/multichain/index.js index 49c3563e4e24..d34e427510d9 100644 --- a/ui/components/multichain/index.js +++ b/ui/components/multichain/index.js @@ -1,3 +1,6 @@ export { AccountListItem } from './account-list-item'; export { AccountListItemMenu } from './account-list-item-menu'; export { AccountListMenu } from './account-list-menu'; +export { DetectedTokensBanner } from './detected-token-banner'; +export { MultichainImportTokenLink } from './multichain-import-token-link'; +export { MultichainTokenListItem } from './multichain-token-list-item'; diff --git a/ui/components/multichain/index.scss b/ui/components/multichain/index.scss deleted file mode 100644 index bef86b1dde89..000000000000 --- a/ui/components/multichain/index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import 'account-list-item/index'; -@import 'account-list-menu/index'; diff --git a/ui/components/multichain/multichain-components.scss b/ui/components/multichain/multichain-components.scss new file mode 100644 index 000000000000..e8ee40ffa49e --- /dev/null +++ b/ui/components/multichain/multichain-components.scss @@ -0,0 +1,9 @@ +/** +* Please import your styles in order of atomicity. +* The most atomic styles should be imported first. +* This will help improve specificity and reduce the chance of +* unintended overrides. +**/ +@import 'account-list-item/index'; +@import 'account-list-menu/index'; +@import 'multichain-token-list-item/multichain-token-list-item'; diff --git a/ui/components/multichain/multichain-import-token-link/__snapshots__/multichain-import-token-link.test.js.snap b/ui/components/multichain/multichain-import-token-link/__snapshots__/multichain-import-token-link.test.js.snap new file mode 100644 index 000000000000..925788efa313 --- /dev/null +++ b/ui/components/multichain/multichain-import-token-link/__snapshots__/multichain-import-token-link.test.js.snap @@ -0,0 +1,75 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Import Token Link should match snapshot for goerli chainId 1`] = ` +
+ +
+`; + +exports[`Import Token Link should match snapshot for mainnet chainId 1`] = ` +
+ +
+`; diff --git a/ui/components/multichain/multichain-import-token-link/index.js b/ui/components/multichain/multichain-import-token-link/index.js new file mode 100644 index 000000000000..c9899d1191b9 --- /dev/null +++ b/ui/components/multichain/multichain-import-token-link/index.js @@ -0,0 +1 @@ +export { MultichainImportTokenLink } from './multichain-import-token-link'; diff --git a/ui/components/multichain/multichain-import-token-link/multichain-import-token-link.js b/ui/components/multichain/multichain-import-token-link/multichain-import-token-link.js new file mode 100644 index 000000000000..fc6dae84a9c6 --- /dev/null +++ b/ui/components/multichain/multichain-import-token-link/multichain-import-token-link.js @@ -0,0 +1,87 @@ +import React, { useContext } from 'react'; +import { useSelector } from 'react-redux'; +import { useHistory } from 'react-router-dom'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import Box from '../../ui/box/box'; +import { ButtonLink, ICON_NAMES } from '../../component-library'; +import { + AlignItems, + DISPLAY, + Size, +} from '../../../helpers/constants/design-system'; +import { useI18nContext } from '../../../hooks/useI18nContext'; +import { IMPORT_TOKEN_ROUTE } from '../../../helpers/constants/routes'; +import { detectNewTokens } from '../../../store/actions'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; +import { EVENT, EVENT_NAMES } from '../../../../shared/constants/metametrics'; +import { + getIsTokenDetectionSupported, + getIsTokenDetectionInactiveOnMainnet, +} from '../../../selectors'; + +export const MultichainImportTokenLink = ({ className, ...props }) => { + const trackEvent = useContext(MetaMetricsContext); + const t = useI18nContext(); + const history = useHistory(); + + const isTokenDetectionSupported = useSelector(getIsTokenDetectionSupported); + const isTokenDetectionInactiveOnMainnet = useSelector( + getIsTokenDetectionInactiveOnMainnet, + ); + + const isTokenDetectionAvailable = + isTokenDetectionSupported || + isTokenDetectionInactiveOnMainnet || + Boolean(process.env.IN_TEST); + return ( + + + { + history.push(IMPORT_TOKEN_ROUTE); + trackEvent({ + event: EVENT_NAMES.TOKEN_IMPORT_BUTTON_CLICKED, + category: EVENT.CATEGORIES.NAVIGATION, + properties: { + location: 'Home', + }, + }); + }} + > + {isTokenDetectionAvailable + ? t('importTokensCamelCase') + : t('importTokensCamelCase').charAt(0).toUpperCase() + + t('importTokensCamelCase').slice(1)} + + + + detectNewTokens()} + > + {t('refreshList')} + + + + ); +}; + +MultichainImportTokenLink.propTypes = { + /** + * An additional className to apply to the TokenList. + */ + className: PropTypes.string, +}; diff --git a/ui/components/multichain/multichain-import-token-link/multichain-import-token-link.stories.js b/ui/components/multichain/multichain-import-token-link/multichain-import-token-link.stories.js new file mode 100644 index 000000000000..1cb7b4395148 --- /dev/null +++ b/ui/components/multichain/multichain-import-token-link/multichain-import-token-link.stories.js @@ -0,0 +1,11 @@ +import React from 'react'; +import { MultichainImportTokenLink } from './multichain-import-token-link'; + +export default { + title: 'Components/Multichain/MultichainImportTokenLink', + component: MultichainImportTokenLink, +}; + +export const DefaultStory = () => ; + +DefaultStory.storyName = 'Default'; diff --git a/ui/components/multichain/multichain-import-token-link/multichain-import-token-link.test.js b/ui/components/multichain/multichain-import-token-link/multichain-import-token-link.test.js new file mode 100644 index 000000000000..dd376c52ba0c --- /dev/null +++ b/ui/components/multichain/multichain-import-token-link/multichain-import-token-link.test.js @@ -0,0 +1,101 @@ +import React from 'react'; +import configureMockStore from 'redux-mock-store'; +import { fireEvent, screen } from '@testing-library/react'; +import { detectNewTokens } from '../../../store/actions'; +import { renderWithProvider } from '../../../../test/lib/render-helpers'; +import { MultichainImportTokenLink } from './multichain-import-token-link'; + +const mockPushHistory = jest.fn(); + +jest.mock('react-router-dom', () => { + const original = jest.requireActual('react-router-dom'); + return { + ...original, + useLocation: jest.fn(() => ({ search: '' })), + useHistory: () => ({ + push: mockPushHistory, + }), + }; +}); + +jest.mock('../../../store/actions.ts', () => ({ + detectNewTokens: jest.fn(), +})); + +describe('Import Token Link', () => { + it('should match snapshot for goerli chainId', () => { + const mockState = { + metamask: { + provider: { + chainId: '0x5', + }, + }, + }; + + const store = configureMockStore()(mockState); + + const { container } = renderWithProvider( + , + store, + ); + + expect(container).toMatchSnapshot(); + }); + + it('should match snapshot for mainnet chainId', () => { + const mockState = { + metamask: { + provider: { + chainId: '0x1', + }, + }, + }; + + const store = configureMockStore()(mockState); + + const { container } = renderWithProvider( + , + store, + ); + + expect(container).toMatchSnapshot(); + }); + + it('should detectNewTokens when clicking refresh', () => { + const mockState = { + metamask: { + provider: { + chainId: '0x5', + }, + }, + }; + + const store = configureMockStore()(mockState); + + renderWithProvider(, store); + + const refreshList = screen.getByTestId('refresh-list-button'); + fireEvent.click(refreshList); + + expect(detectNewTokens).toHaveBeenCalled(); + }); + + it('should push import token route', () => { + const mockState = { + metamask: { + provider: { + chainId: '0x5', + }, + }, + }; + + const store = configureMockStore()(mockState); + + renderWithProvider(, store); + + const importToken = screen.getByTestId('import-token-button'); + fireEvent.click(importToken); + + expect(mockPushHistory).toHaveBeenCalledWith('/import-token'); + }); +}); diff --git a/ui/components/multichain/multichain-token-list-item/__snapshots__/multichain-token-list-item.test.js.snap b/ui/components/multichain/multichain-token-list-item/__snapshots__/multichain-token-list-item.test.js.snap new file mode 100644 index 000000000000..b4af82ccb0a6 --- /dev/null +++ b/ui/components/multichain/multichain-token-list-item/__snapshots__/multichain-token-list-item.test.js.snap @@ -0,0 +1,67 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MultichainTokenListItem should render correctly 1`] = ` + +`; diff --git a/ui/components/multichain/multichain-token-list-item/index.js b/ui/components/multichain/multichain-token-list-item/index.js new file mode 100644 index 000000000000..bccfac8ab4e9 --- /dev/null +++ b/ui/components/multichain/multichain-token-list-item/index.js @@ -0,0 +1 @@ +export { MultichainTokenListItem } from './multichain-token-list-item'; diff --git a/ui/components/multichain/multichain-token-list-item/multichain-token-list-item.js b/ui/components/multichain/multichain-token-list-item/multichain-token-list-item.js new file mode 100644 index 000000000000..82ed7752403a --- /dev/null +++ b/ui/components/multichain/multichain-token-list-item/multichain-token-list-item.js @@ -0,0 +1,162 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { useSelector } from 'react-redux'; +import classnames from 'classnames'; +import { + BLOCK_SIZES, + BorderColor, + DISPLAY, + FLEX_DIRECTION, + FONT_WEIGHT, + JustifyContent, + Size, + TextColor, + TextVariant, + TEXT_ALIGN, +} from '../../../helpers/constants/design-system'; +import { + AvatarNetwork, + AvatarToken, + BadgeWrapper, + Text, +} from '../../component-library'; +import Box from '../../ui/box/box'; +import { getNativeCurrencyImage } from '../../../selectors'; +import Tooltip from '../../ui/tooltip'; +import { useI18nContext } from '../../../hooks/useI18nContext'; + +export const MultichainTokenListItem = ({ + className, + onClick, + tokenSymbol, + tokenImage, + primary, + secondary, + title, +}) => { + const t = useI18nContext(); + const primaryTokenImage = useSelector(getNativeCurrencyImage); + const dataTheme = document.documentElement.getAttribute('data-theme'); + return ( + + { + e.preventDefault(); + onClick(); + }} + > + + } + marginRight={3} + > + + + + + + + + {title === 'ETH' ? t('networkNameEthereum') : title} + + + + + {secondary} + + + + {Number(primary).toFixed(3)} {tokenSymbol}{' '} + + + + + ); +}; + +MultichainTokenListItem.propTypes = { + /** + * An additional className to apply to the TokenList. + */ + className: PropTypes.string, + /** + * The onClick handler to be passed to the MultichainTokenListItem component + */ + onClick: PropTypes.func, + /** + * tokenSymbol represents the symbol of the Token + */ + tokenSymbol: PropTypes.string, + /** + * title represents the name of the token and if name is not available then Symbol + */ + title: PropTypes.string, + /** + * tokenImage represnts the image of the token icon + */ + tokenImage: PropTypes.string, + /** + * primary represents the balance + */ + primary: PropTypes.string, + /** + * secondary represents the balance in dollars + */ + secondary: PropTypes.string, +}; diff --git a/ui/components/multichain/multichain-token-list-item/multichain-token-list-item.scss b/ui/components/multichain/multichain-token-list-item/multichain-token-list-item.scss new file mode 100644 index 000000000000..f6863f8b32b2 --- /dev/null +++ b/ui/components/multichain/multichain-token-list-item/multichain-token-list-item.scss @@ -0,0 +1,8 @@ +.multichain-token-list-item { + &__container-cell { + &:hover, + &:focus-within { + background-color: var(--color-background-default-hover); + } + } +} diff --git a/ui/components/multichain/multichain-token-list-item/multichain-token-list-item.stories.js b/ui/components/multichain/multichain-token-list-item/multichain-token-list-item.stories.js new file mode 100644 index 000000000000..168e6ed1aa0f --- /dev/null +++ b/ui/components/multichain/multichain-token-list-item/multichain-token-list-item.stories.js @@ -0,0 +1,78 @@ +import React from 'react'; +import { Provider } from 'react-redux'; +import testData from '../../../../.storybook/test-data'; +import configureStore from '../../../store/store'; +import { MultichainTokenListItem } from './multichain-token-list-item'; + +export default { + title: 'Components/Multichain/MultichainTokenListItem', + component: MultichainTokenListItem, + argTypes: { + tokenSymbol: { + control: 'text', + }, + tokenImage: { + control: 'text', + }, + primary: { + control: 'text', + }, + secondary: { + control: 'text', + }, + title: { + control: 'text', + }, + onClick: { + action: 'onClick', + }, + }, + args: { + secondary: '$9.80 USD', + primary: '88.00687889', + tokenImage: './images/eth_logo.svg', + tokenSymbol: 'ETH', + title: 'Ethereum', + }, +}; + +const customNetworkData = { + ...testData, + metamask: { ...testData.metamask, nativeCurrency: '' }, +}; +const customNetworkStore = configureStore(customNetworkData); + +const Template = (args) => { + return ; +}; + +export const DefaultStory = Template.bind({}); + +export const ChaosStory = (args) => ( +
+ +
+); +ChaosStory.storyName = 'ChaosStory'; + +ChaosStory.args = { + title: 'Really long, long name', + secondary: '$94556756776.80 USD', + primary: '34449765768526.00', +}; + +export const NoImagesStory = Template.bind({}); + +NoImagesStory.decorators = [ + (Story) => ( + + + + ), +]; + +NoImagesStory.args = { + tokenImage: '', +}; diff --git a/ui/components/multichain/multichain-token-list-item/multichain-token-list-item.test.js b/ui/components/multichain/multichain-token-list-item/multichain-token-list-item.test.js new file mode 100644 index 000000000000..6f04982984ae --- /dev/null +++ b/ui/components/multichain/multichain-token-list-item/multichain-token-list-item.test.js @@ -0,0 +1,57 @@ +import React from 'react'; +import configureMockStore from 'redux-mock-store'; + +import { fireEvent } from '@testing-library/react'; +import { renderWithProvider } from '../../../../test/lib/render-helpers'; +import { MultichainTokenListItem } from './multichain-token-list-item'; + +const state = { + metamask: { + provider: { + ticker: 'ETH', + nickname: '', + chainId: '0x1', + type: 'mainnet', + }, + useTokenDetection: false, + nativeCurrency: 'ETH', + }, +}; + +describe('MultichainTokenListItem', () => { + const props = { + onClick: jest.fn(), + }; + it('should render correctly', () => { + const store = configureMockStore()(state); + const { getByTestId, container } = renderWithProvider( + , + store, + ); + expect(getByTestId('multichain-token-list-item')).toBeDefined(); + expect(container).toMatchSnapshot(); + }); + + it('should render with custom className', () => { + const store = configureMockStore()(state); + const { getByTestId } = renderWithProvider( + , + store, + ); + expect(getByTestId('multichain-token-list-item')).toHaveClass( + 'multichain-token-list-item-test', + ); + }); + + it('handles click action and fires onClick', () => { + const store = configureMockStore()(state); + const { queryByTestId } = renderWithProvider( + , + store, + ); + + fireEvent.click(queryByTestId('multichain-token-list-button')); + + expect(props.onClick).toHaveBeenCalled(); + }); +}); diff --git a/ui/components/ui/tooltip/index.scss b/ui/components/ui/tooltip/index.scss index 8ae602e33f7b..3bc86f9102b2 100644 --- a/ui/components/ui/tooltip/index.scss +++ b/ui/components/ui/tooltip/index.scss @@ -26,19 +26,19 @@ } } - &[x-placement^=top] .tippy-tooltip.tippy-tooltip--mm-custom-theme [x-arrow] { + &[x-placement^='top'] .tippy-tooltip.tippy-tooltip--mm-custom-theme [x-arrow] { border-top-color: var(--color-background-default); } - &[x-placement^=right] .tippy-tooltip.tippy-tooltip--mm-custom-theme [x-arrow] { + &[x-placement^='right'] .tippy-tooltip.tippy-tooltip--mm-custom-theme [x-arrow] { border-right-color: var(--color-background-default); } - &[x-placement^=left] .tippy-tooltip.tippy-tooltip--mm-custom-theme [x-arrow] { + &[x-placement^='left'] .tippy-tooltip.tippy-tooltip--mm-custom-theme [x-arrow] { border-left-color: var(--color-background-default); } - &[x-placement^=bottom] .tippy-tooltip.tippy-tooltip--mm-custom-theme [x-arrow] { + &[x-placement^='bottom'] .tippy-tooltip.tippy-tooltip--mm-custom-theme [x-arrow] { border-bottom-color: var(--color-background-default); } } diff --git a/ui/css/index.scss b/ui/css/index.scss index 46e1e7f2b095..a26c7ae09cf6 100644 --- a/ui/css/index.scss +++ b/ui/css/index.scss @@ -10,8 +10,8 @@ @import './base-styles.scss'; @import '../components/component-library/component-library-components.scss'; @import '../components/app/app-components'; -@import '../components/multichain/index.scss'; @import '../components/ui/ui-components'; +@import '../components/multichain/multichain-components.scss'; @import '../pages/pages'; @import './errors.scss'; @import './loading.scss';