diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index cc8e75b3bb7b..30d9bd34671b 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -301,6 +301,12 @@ function setSentryClient() { */ globalThis.nw = {}; + /** + * Sentry checks session tracking support by looking for global history object and functions inside it. + * Scuttling sets this property to undefined which breaks Sentry logic and crashes background. + */ + globalThis.history ??= {}; + log('Updating client', { environment, dsn, diff --git a/development/build/scripts.js b/development/build/scripts.js index 926acea8252d..121e000a5673 100644 --- a/development/build/scripts.js +++ b/development/build/scripts.js @@ -89,18 +89,16 @@ const scuttlingConfigBase = { extra: '', stateHooks: '', nw: '', + // Sentry Auto Session Tracking + document: '', + history: '', + isNaN: '', + parseInt: '', }, }; const mv3ScuttlingConfig = { ...scuttlingConfigBase }; - -const standardScuttlingConfig = { - ...scuttlingConfigBase, - 'scripts/sentry-install.js': { - ...scuttlingConfigBase['scripts/sentry-install.js'], - document: '', - }, -}; +const standardScuttlingConfig = { ...scuttlingConfigBase }; const noopWriteStream = through.obj((_file, _fileEncoding, callback) => callback(), diff --git a/shared/constants/network.ts b/shared/constants/network.ts index de8429d8ba6d..641e310517cb 100644 --- a/shared/constants/network.ts +++ b/shared/constants/network.ts @@ -143,6 +143,7 @@ export const CHAIN_IDS = { SEI: '0x531', BERACHAIN: '0x138d5', METACHAIN_ONE: '0x1b6e6', + ARBITRUM_SEPOLIA: '0x66eee', NEAR: '0x18d', NEAR_TESTNET: '0x18e', } as const; @@ -1034,4 +1035,5 @@ export const TEST_NETWORK_IDS = [ CHAIN_IDS.SEPOLIA, CHAIN_IDS.LINEA_GOERLI, CHAIN_IDS.LINEA_SEPOLIA, + CHAIN_IDS.ARBITRUM_SEPOLIA, ]; diff --git a/test/e2e/fixture-builder.js b/test/e2e/fixture-builder.js index 54bfd5eeeb89..357cd95600f4 100644 --- a/test/e2e/fixture-builder.js +++ b/test/e2e/fixture-builder.js @@ -174,6 +174,18 @@ class FixtureBuilder { return this; } + withConversionRateDisabled() { + return this.withPreferencesController({ + useCurrencyRateCheck: false, + }); + } + + withConversionRateEnabled() { + return this.withPreferencesController({ + useCurrencyRateCheck: true, + }); + } + withGasFeeController(data) { merge(this.fixture.data.GasFeeController, data); return this; diff --git a/test/e2e/helpers/mock-server.ts b/test/e2e/helpers/mock-server.ts new file mode 100644 index 000000000000..9fd64c248e28 --- /dev/null +++ b/test/e2e/helpers/mock-server.ts @@ -0,0 +1,28 @@ +import { strict as assert } from 'assert'; +import { Driver } from '../webdriver/driver'; +import { MockedEndpoint } from '../mock-e2e'; + +const TIMEOUT_DEFAULT = 10 * 1000; // 10 Seconds + +export async function expectMockRequest( + driver: Driver, + mockedEndpoint: MockedEndpoint, + { timeout }: { timeout?: number } = {}, +) { + await driver.wait(async () => { + const isPending = await mockedEndpoint.isPending(); + return isPending === false; + }, timeout ?? TIMEOUT_DEFAULT); +} + +export async function expectNoMockRequest( + driver: Driver, + mockedEndpoint: MockedEndpoint, + { timeout }: { timeout?: number } = {}, +) { + await driver.delay(timeout ?? TIMEOUT_DEFAULT); + + const isPending = await mockedEndpoint.isPending(); + + assert.ok(isPending, 'Expected no requests'); +} diff --git a/test/e2e/tests/metrics/sessions.spec.ts b/test/e2e/tests/metrics/sessions.spec.ts new file mode 100644 index 000000000000..b5666a9078b8 --- /dev/null +++ b/test/e2e/tests/metrics/sessions.spec.ts @@ -0,0 +1,72 @@ +import { MockttpServer } from 'mockttp'; +import FixtureBuilder from '../../fixture-builder'; +import { + defaultGanacheOptions, + unlockWallet, + withFixtures, +} from '../../helpers'; +import { + expectMockRequest, + expectNoMockRequest, +} from '../../helpers/mock-server'; + +async function mockSentrySession(mockServer: MockttpServer) { + return [ + await mockServer + .forPost(/sentry/u) + .withBodyIncluding('"type":"session"') + .withBodyIncluding('"status":"exited"') + .thenCallback(() => { + return { + statusCode: 200, + json: {}, + }; + }), + ]; +} + +describe('Sessions', function () { + it('sends session in UI if metrics enabled', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + participateInMetaMetrics: true, + }) + .build(), + ganacheOptions: defaultGanacheOptions, + title: this.test?.fullTitle(), + testSpecificMock: mockSentrySession, + manifestFlags: { + doNotForceSentryForThisTest: true, + }, + }, + async ({ driver, mockedEndpoint }) => { + await unlockWallet(driver); + await expectMockRequest(driver, mockedEndpoint[0], { timeout: 3000 }); + }, + ); + }); + + it('does not send session in UI if metrics disabled @no-mmi', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + participateInMetaMetrics: false, + }) + .build(), + ganacheOptions: defaultGanacheOptions, + title: this.test?.fullTitle(), + testSpecificMock: mockSentrySession, + manifestFlags: { + doNotForceSentryForThisTest: true, + }, + }, + async ({ driver, mockedEndpoint }) => { + await unlockWallet(driver); + await expectNoMockRequest(driver, mockedEndpoint[0], { timeout: 3000 }); + }, + ); + }); +}); diff --git a/test/e2e/tests/settings/account-token-list.spec.js b/test/e2e/tests/settings/account-token-list.spec.js index 921cacae9e3a..0fae71ae1d85 100644 --- a/test/e2e/tests/settings/account-token-list.spec.js +++ b/test/e2e/tests/settings/account-token-list.spec.js @@ -11,7 +11,7 @@ describe('Settings', function () { it('Should match the value of token list item and account list item for eth conversion', async function () { await withFixtures( { - fixtures: new FixtureBuilder().build(), + fixtures: new FixtureBuilder().withConversionRateDisabled().build(), ganacheOptions: defaultGanacheOptions, title: this.test.fullTitle(), }, @@ -41,7 +41,7 @@ describe('Settings', function () { it('Should match the value of token list item and account list item for fiat conversion', async function () { await withFixtures( { - fixtures: new FixtureBuilder().build(), + fixtures: new FixtureBuilder().withConversionRateEnabled().build(), ganacheOptions: defaultGanacheOptions, title: this.test.fullTitle(), }, @@ -57,16 +57,6 @@ describe('Settings', function () { tag: 'div', }); await driver.clickElement({ text: 'Fiat', tag: 'label' }); - // We now need to enable "Show fiat on testnet" if we are using testnets (and since our custom - // network during test is using a testnet chain ID, it will be considered as a test network) - await driver.clickElement({ - text: 'Advanced', - tag: 'div', - }); - await driver.clickElement('.show-fiat-on-testnets-toggle'); - // Looks like when enabling the "Show fiat on testnet" it takes some time to re-update the - // overview screen, so just wait a bit here: - await driver.delay(1000); await driver.clickElement( '.settings-page__header__title-container__close-button', @@ -78,7 +68,9 @@ describe('Settings', function () { const tokenListAmount = await driver.findElement( '.eth-overview__primary-container', ); + await driver.delay(1000); assert.equal(await tokenListAmount.getText(), '$42,500.00\nUSD'); + await driver.clickElement('[data-testid="account-menu-icon"]'); const accountTokenValue = await driver.waitForSelector( '.multichain-account-list-item .multichain-account-list-item__asset', diff --git a/test/e2e/tests/simulation-details/mock-request-error-malformed-transaction.ts b/test/e2e/tests/simulation-details/mock-request-error-malformed-transaction.ts index 04e98b2de589..49e4ad7e989a 100644 --- a/test/e2e/tests/simulation-details/mock-request-error-malformed-transaction.ts +++ b/test/e2e/tests/simulation-details/mock-request-error-malformed-transaction.ts @@ -11,7 +11,6 @@ export const MALFORMED_TRANSACTION_MOCK = { export const MALFORMED_TRANSACTION_REQUEST_MOCK: MockRequestResponse = { request: { - id: '21', jsonrpc: '2.0', method: 'infura_simulateTransactions', params: [ diff --git a/test/e2e/tests/simulation-details/simulation-details.spec.ts b/test/e2e/tests/simulation-details/simulation-details.spec.ts index ea0f7b9f2632..46f4cdb5860a 100644 --- a/test/e2e/tests/simulation-details/simulation-details.spec.ts +++ b/test/e2e/tests/simulation-details/simulation-details.spec.ts @@ -120,7 +120,7 @@ export async function mockRequest( ) { await server .forPost(TX_SENTINEL_URL) - .withJsonBody(request) + .withJsonBodyIncluding(request) .thenJson(200, response); } diff --git a/test/e2e/tests/swaps/swaps-notifications.spec.js b/test/e2e/tests/swaps/swaps-notifications.spec.js index 0a56f1cb6170..c6dbbd469959 100644 --- a/test/e2e/tests/swaps/swaps-notifications.spec.js +++ b/test/e2e/tests/swaps/swaps-notifications.spec.js @@ -1,4 +1,5 @@ const { withFixtures, unlockWallet } = require('../../helpers'); +const { SWAP_TEST_ETH_USDC_TRADES_MOCK } = require('../../../data/mock-data'); const { withFixturesOptions, buildQuote, @@ -6,6 +7,17 @@ const { checkNotification, } = require('./shared'); +async function mockSwapsTransactionQuote(mockServer) { + return [ + await mockServer + .forGet('https://swap.api.cx.metamask.io/networks/1/trades') + .thenCallback(() => ({ + statusCode: 200, + json: SWAP_TEST_ETH_USDC_TRADES_MOCK, + })), + ]; +} + describe('Swaps - notifications @no-mmi', function () { async function mockTradesApiPriceSlippageError(mockServer) { await mockServer @@ -95,25 +107,37 @@ describe('Swaps - notifications @no-mmi', function () { ); }); it('tests a notification for not enough balance', async function () { + const lowBalanceGanacheOptions = { + accounts: [ + { + secretKey: + '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', + balance: 0, + }, + ], + }; + await withFixtures( { ...withFixturesOptions, + ganacheOptions: lowBalanceGanacheOptions, + testSpecificMock: mockSwapsTransactionQuote, title: this.test.fullTitle(), }, async ({ driver }) => { await unlockWallet(driver); await buildQuote(driver, { - amount: 50, + amount: 0.001, swapTo: 'USDC', }); await checkNotification(driver, { title: 'Insufficient balance', - text: 'You need 43.4467 more TESTETH to complete this swap', + text: 'You need 0.001 more TESTETH to complete this swap', }); await reviewQuote(driver, { swapFrom: 'TESTETH', swapTo: 'USDC', - amount: 50, + amount: 0.001, skipCounter: true, }); await driver.waitForSelector({ diff --git a/test/e2e/tests/tokens/custom-token-send-transfer.spec.js b/test/e2e/tests/tokens/custom-token-send-transfer.spec.js index a5ae0fea449e..fa87c9cfbe64 100644 --- a/test/e2e/tests/tokens/custom-token-send-transfer.spec.js +++ b/test/e2e/tests/tokens/custom-token-send-transfer.spec.js @@ -59,7 +59,7 @@ describe('Transfer custom tokens @no-mmi', function () { '.currency-display-component__text', ); assert.notEqual( - await estimatedGasFee[0].getText(), + await estimatedGasFee[1].getText(), '0', 'Estimated gas fee should not be 0', ); diff --git a/test/e2e/tests/transaction/send-edit.spec.js b/test/e2e/tests/transaction/send-edit.spec.js index a3665bd33591..95e5b3235ebd 100644 --- a/test/e2e/tests/transaction/send-edit.spec.js +++ b/test/e2e/tests/transaction/send-edit.spec.js @@ -13,6 +13,7 @@ describe('Editing Confirm Transaction', function () { { fixtures: new FixtureBuilder() .withTransactionControllerTypeOneTransaction() + .withConversionRateDisabled() .build(), ganacheOptions: defaultGanacheOptions, title: this.test.fullTitle(), @@ -87,6 +88,7 @@ describe('Editing Confirm Transaction', function () { { fixtures: new FixtureBuilder() .withTransactionControllerTypeTwoTransaction() + .withConversionRateDisabled() .build(), ganacheOptions: generateGanacheOptions({ hardfork: 'london' }), title: this.test.fullTitle(), diff --git a/ui/components/app/assets/asset-list/asset-list.test.js b/ui/components/app/assets/asset-list/asset-list.test.tsx similarity index 88% rename from ui/components/app/assets/asset-list/asset-list.test.js rename to ui/components/app/assets/asset-list/asset-list.test.tsx index e0ec77157194..329c29a6108e 100644 --- a/ui/components/app/assets/asset-list/asset-list.test.js +++ b/ui/components/app/assets/asset-list/asset-list.test.tsx @@ -1,14 +1,14 @@ import React from 'react'; import { screen, act, waitFor } from '@testing-library/react'; import { renderWithProvider } from '../../../../../test/jest'; -import configureStore from '../../../../store/store'; +import configureStore, { MetaMaskReduxState } from '../../../../store/store'; import mockState from '../../../../../test/data/mock-state.json'; import { CHAIN_IDS } from '../../../../../shared/constants/network'; import { useIsOriginalNativeTokenSymbol } from '../../../../hooks/useIsOriginalNativeTokenSymbol'; import { getTokenSymbol } from '../../../../store/actions'; import { getSelectedInternalAccountFromMockState } from '../../../../../test/jest/mocks'; import { mockNetworkState } from '../../../../../test/stub/networks'; -import AssetList from './asset-list'; +import AssetList from '.'; // Specific to just the ETH FIAT conversion const CONVERSION_RATE = 1597.32; @@ -67,8 +67,9 @@ jest.mock('../../../../store/actions', () => { }; }); -const mockSelectedInternalAccount = - getSelectedInternalAccountFromMockState(mockState); +const mockSelectedInternalAccount = getSelectedInternalAccountFromMockState( + mockState as unknown as MetaMaskReduxState, +); const render = (balance = ETH_BALANCE, chainId = CHAIN_IDS.MAINNET) => { const state = { @@ -102,15 +103,15 @@ const render = (balance = ETH_BALANCE, chainId = CHAIN_IDS.MAINNET) => { }; const store = configureStore(state); return renderWithProvider( - undefined} />, + undefined} showTokensLinks />, store, ); }; describe('AssetList', () => { - useIsOriginalNativeTokenSymbol.mockReturnValue(true); + (useIsOriginalNativeTokenSymbol as jest.Mock).mockReturnValue(true); - getTokenSymbol.mockImplementation(async (address) => { + (getTokenSymbol as jest.Mock).mockImplementation(async (address) => { if (address === USDC_CONTRACT) { return 'USDC'; } diff --git a/ui/components/app/assets/asset-list/asset-list.js b/ui/components/app/assets/asset-list/asset-list.tsx similarity index 92% rename from ui/components/app/assets/asset-list/asset-list.js rename to ui/components/app/assets/asset-list/asset-list.tsx index 916d86a07c26..ebc78c3ab378 100644 --- a/ui/components/app/assets/asset-list/asset-list.js +++ b/ui/components/app/assets/asset-list/asset-list.tsx @@ -1,5 +1,4 @@ import React, { useContext, useState } from 'react'; -import PropTypes from 'prop-types'; import { useSelector } from 'react-redux'; import TokenList from '../token-list'; import { PRIMARY, SECONDARY } from '../../../../helpers/constants/common'; @@ -54,7 +53,19 @@ import { import { getIsNativeTokenBuyable } from '../../../../ducks/ramps'; ///: END:ONLY_INCLUDE_IF -const AssetList = ({ onClickAsset, showTokensLinks }) => { +export type TokenWithBalance = { + address: string; + symbol: string; + string: string; + image: string; +}; + +type AssetListProps = { + onClickAsset: (arg: string) => void; + showTokensLinks: boolean; +}; + +const AssetList = ({ onClickAsset, showTokensLinks }: AssetListProps) => { const [showDetectedTokens, setShowDetectedTokens] = useState(false); const nativeCurrency = useSelector(getMultichainNativeCurrency); const showFiat = useSelector(getMultichainShouldShowFiat); @@ -113,13 +124,21 @@ const AssetList = ({ onClickAsset, showTokensLinks }) => { setShowReceiveModal(true); }; - const { tokensWithBalances, loading } = useAccountTotalFiatBalance( + const accountTotalFiatBalance = useAccountTotalFiatBalance( selectedAccount, shouldHideZeroBalanceTokens, ); + + const tokensWithBalances = + accountTotalFiatBalance.tokensWithBalances as TokenWithBalance[]; + + const { loading } = accountTotalFiatBalance; + tokensWithBalances.forEach((token) => { - // token.string is the balance displayed in the TokenList UI - token.string = roundToDecimalPlacesRemovingExtraZeroes(token.string, 5); + token.string = roundToDecimalPlacesRemovingExtraZeroes( + token.string, + 5, + ) as string; }); const balanceIsZero = useSelector( @@ -150,6 +169,7 @@ const AssetList = ({ onClickAsset, showTokensLinks }) => { {detectedTokens.length > 0 && !isTokenDetectionInactiveOnNonMainnetSupportedNetwork && ( setShowDetectedTokens(true)} margin={4} /> @@ -206,7 +226,7 @@ const AssetList = ({ onClickAsset, showTokensLinks }) => { { + onTokenClick={(tokenAddress: string) => { onClickAsset(tokenAddress); trackEvent({ event: MetaMetricsEventName.TokenScreenOpened, @@ -246,9 +266,4 @@ const AssetList = ({ onClickAsset, showTokensLinks }) => { ); }; -AssetList.propTypes = { - onClickAsset: PropTypes.func.isRequired, - showTokensLinks: PropTypes.bool, -}; - export default AssetList; diff --git a/ui/components/app/assets/asset-list/index.js b/ui/components/app/assets/asset-list/index.ts similarity index 100% rename from ui/components/app/assets/asset-list/index.js rename to ui/components/app/assets/asset-list/index.ts diff --git a/ui/components/app/assets/token-cell/__snapshots__/token-cell.test.js.snap b/ui/components/app/assets/token-cell/__snapshots__/token-cell.test.tsx.snap similarity index 100% rename from ui/components/app/assets/token-cell/__snapshots__/token-cell.test.js.snap rename to ui/components/app/assets/token-cell/__snapshots__/token-cell.test.tsx.snap diff --git a/ui/components/app/assets/token-cell/index.js b/ui/components/app/assets/token-cell/index.ts similarity index 100% rename from ui/components/app/assets/token-cell/index.js rename to ui/components/app/assets/token-cell/index.ts diff --git a/ui/components/app/assets/token-cell/token-cell.test.js b/ui/components/app/assets/token-cell/token-cell.test.tsx similarity index 90% rename from ui/components/app/assets/token-cell/token-cell.test.js rename to ui/components/app/assets/token-cell/token-cell.test.tsx index 6404f3603e58..d92be927acdc 100644 --- a/ui/components/app/assets/token-cell/token-cell.test.js +++ b/ui/components/app/assets/token-cell/token-cell.test.tsx @@ -47,7 +47,7 @@ describe('Token Cell', () => { }, }; - useIsOriginalTokenSymbol.mockReturnValue(true); + (useIsOriginalTokenSymbol as jest.Mock).mockReturnValue(true); // two tokens with the same symbol but different addresses const MOCK_GET_TOKEN_LIST = { @@ -78,6 +78,7 @@ describe('Token Cell', () => { symbol: 'TEST', string: '5.000', currentCurrency: 'usd', + image: '', onClick: jest.fn(), }; @@ -86,10 +87,11 @@ describe('Token Cell', () => { symbol: 'TEST', string: '5000000', currentCurrency: 'usd', + image: '', onClick: jest.fn(), }; - useSelector.mockReturnValue(MOCK_GET_TOKEN_LIST); - useTokenFiatAmount.mockReturnValue('5.00'); + (useSelector as jest.Mock).mockReturnValue(MOCK_GET_TOKEN_LIST); + (useTokenFiatAmount as jest.Mock).mockReturnValue('5.00'); it('should match snapshot', () => { const { container } = renderWithProvider( @@ -106,7 +108,9 @@ describe('Token Cell', () => { mockStore, ); - fireEvent.click(queryByTestId('multichain-token-list-button')); + const targetElem = queryByTestId('multichain-token-list-button'); + + targetElem && fireEvent.click(targetElem); expect(props.onClick).toHaveBeenCalled(); }); diff --git a/ui/components/app/assets/token-cell/token-cell.js b/ui/components/app/assets/token-cell/token-cell.tsx similarity index 81% rename from ui/components/app/assets/token-cell/token-cell.js rename to ui/components/app/assets/token-cell/token-cell.tsx index a488682ea4a8..2cd5cb84b8ab 100644 --- a/ui/components/app/assets/token-cell/token-cell.js +++ b/ui/components/app/assets/token-cell/token-cell.tsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { useSelector } from 'react-redux'; import { getTokenList } from '../../../../selectors'; @@ -8,7 +7,21 @@ import { isEqualCaseInsensitive } from '../../../../../shared/modules/string-uti import { useIsOriginalTokenSymbol } from '../../../../hooks/useIsOriginalTokenSymbol'; import { getIntlLocale } from '../../../../ducks/locale/locale'; -export default function TokenCell({ address, image, symbol, string, onClick }) { +type TokenCellProps = { + address: string; + symbol: string; + string: string; + image: string; + onClick?: (arg: string) => void; +}; + +export default function TokenCell({ + address, + image, + symbol, + string, + onClick, +}: TokenCellProps) { const tokenList = useSelector(getTokenList); const tokenData = Object.values(tokenList).find( (token) => @@ -17,10 +30,12 @@ export default function TokenCell({ address, image, symbol, string, onClick }) { ); const title = tokenData?.name || symbol; const tokenImage = tokenData?.iconUrl || image; - const formattedFiat = useTokenFiatAmount(address, string, symbol); + const formattedFiat = useTokenFiatAmount(address, string, symbol, {}, false); const locale = useSelector(getIntlLocale); const primary = new Intl.NumberFormat(locale, { minimumSignificantDigits: 1, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore }).format(string.toString()); const isOriginalTokenSymbol = useIsOriginalTokenSymbol(address, symbol); @@ -39,11 +54,3 @@ export default function TokenCell({ address, image, symbol, string, onClick }) { /> ); } - -TokenCell.propTypes = { - address: PropTypes.string, - symbol: PropTypes.string, - string: PropTypes.string, - onClick: PropTypes.func, - image: PropTypes.string, -}; diff --git a/ui/components/app/assets/token-list/index.js b/ui/components/app/assets/token-list/index.ts similarity index 100% rename from ui/components/app/assets/token-list/index.js rename to ui/components/app/assets/token-list/index.ts diff --git a/ui/components/app/assets/token-list/token-list.js b/ui/components/app/assets/token-list/token-list.tsx similarity index 73% rename from ui/components/app/assets/token-list/token-list.js rename to ui/components/app/assets/token-list/token-list.tsx index 4dd8f57412db..194ea2762191 100644 --- a/ui/components/app/assets/token-list/token-list.js +++ b/ui/components/app/assets/token-list/token-list.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import TokenCell from '../token-cell'; import { useI18nContext } from '../../../../hooks/useI18nContext'; import { Box } from '../../../component-library'; @@ -8,8 +7,19 @@ import { Display, JustifyContent, } from '../../../../helpers/constants/design-system'; +import { TokenWithBalance } from '../asset-list/asset-list'; -export default function TokenList({ onTokenClick, tokens, loading = false }) { +type TokenListProps = { + onTokenClick: (arg: string) => void; + tokens: TokenWithBalance[]; + loading: boolean; +}; + +export default function TokenList({ + onTokenClick, + tokens, + loading = false, +}: TokenListProps) { const t = useI18nContext(); if (loading) { @@ -34,9 +44,3 @@ export default function TokenList({ onTokenClick, tokens, loading = false }) { ); } - -TokenList.propTypes = { - onTokenClick: PropTypes.func.isRequired, - tokens: PropTypes.array.isRequired, - loading: PropTypes.bool, -}; diff --git a/ui/components/multichain/asset-picker-amount/asset-picker/__snapshots__/asset-picker.test.tsx.snap b/ui/components/multichain/asset-picker-amount/asset-picker/__snapshots__/asset-picker.test.tsx.snap index 06fcfb5c9901..d2748ce758bf 100644 --- a/ui/components/multichain/asset-picker-amount/asset-picker/__snapshots__/asset-picker.test.tsx.snap +++ b/ui/components/multichain/asset-picker-amount/asset-picker/__snapshots__/asset-picker.test.tsx.snap @@ -3,12 +3,11 @@ exports[`AssetPicker matches snapshot 1`] = ` + ); } diff --git a/ui/components/multichain/token-list-item/__snapshots__/token-list-item.test.js.snap b/ui/components/multichain/token-list-item/__snapshots__/token-list-item.test.tsx.snap similarity index 100% rename from ui/components/multichain/token-list-item/__snapshots__/token-list-item.test.js.snap rename to ui/components/multichain/token-list-item/__snapshots__/token-list-item.test.tsx.snap diff --git a/ui/components/multichain/token-list-item/index.js b/ui/components/multichain/token-list-item/index.ts similarity index 100% rename from ui/components/multichain/token-list-item/index.js rename to ui/components/multichain/token-list-item/index.ts diff --git a/ui/components/multichain/token-list-item/token-list-item.test.js b/ui/components/multichain/token-list-item/token-list-item.test.tsx similarity index 87% rename from ui/components/multichain/token-list-item/token-list-item.test.js rename to ui/components/multichain/token-list-item/token-list-item.test.tsx index d62284c6fb19..e67337847b42 100644 --- a/ui/components/multichain/token-list-item/token-list-item.test.js +++ b/ui/components/multichain/token-list-item/token-list-item.test.tsx @@ -35,7 +35,7 @@ const state = { }, }; -let openTabSpy; +let openTabSpy: jest.SpyInstance; jest.mock('../../../ducks/locale/locale', () => ({ getIntlLocale: jest.fn(), @@ -45,18 +45,19 @@ const mockGetIntlLocale = getIntlLocale; describe('TokenListItem', () => { beforeAll(() => { - global.platform = { openTab: jest.fn() }; + global.platform = { openTab: jest.fn(), closeCurrentWindow: jest.fn() }; openTabSpy = jest.spyOn(global.platform, 'openTab'); - mockGetIntlLocale.mockReturnValue('en-US'); + (mockGetIntlLocale as unknown as jest.Mock).mockReturnValue('en-US'); }); const props = { onClick: jest.fn(), + tokenImage: '', + title: '', }; it('should render correctly', () => { const store = configureMockStore()(state); const { getByTestId, container } = renderWithProvider( - // eslint-disable-next-line no-empty-function - {}} />, + , store, ); expect(getByTestId('multichain-token-list-item')).toBeDefined(); @@ -66,7 +67,7 @@ describe('TokenListItem', () => { it('should render with custom className', () => { const store = configureMockStore()(state); const { getByTestId } = renderWithProvider( - , + , store, ); expect(getByTestId('multichain-token-list-item')).toHaveClass( @@ -80,6 +81,8 @@ describe('TokenListItem', () => { primary: '11.9751 ETH', isNativeCurrency: true, isOriginalTokenSymbol: false, + tokenImage: '', + title: '', }; const { getByText } = renderWithProvider( , @@ -95,6 +98,8 @@ describe('TokenListItem', () => { isNativeCurrency: true, isOriginalTokenSymbol: false, showPercentage: true, + tokenImage: '', + title: '', }; const { getByTestId, getByText } = renderWithProvider( , @@ -118,6 +123,8 @@ describe('TokenListItem', () => { primary: '11.9751 ETH', isNativeCurrency: true, isOriginalTokenSymbol: false, + tokenImage: '', + title: '', }; const { getByText } = renderWithProvider( @@ -130,11 +137,13 @@ describe('TokenListItem', () => { it('handles click action and fires onClick', () => { const store = configureMockStore()(state); const { queryByTestId } = renderWithProvider( - , + , store, ); - fireEvent.click(queryByTestId('multichain-token-list-button')); + const targetElem = queryByTestId('multichain-token-list-button'); + + targetElem && fireEvent.click(targetElem); expect(props.onClick).toHaveBeenCalled(); }); @@ -153,7 +162,7 @@ describe('TokenListItem', () => { expect(stakeButton).toBeInTheDocument(); expect(stakeButton).not.toBeDisabled(); - fireEvent.click(stakeButton); + stakeButton && fireEvent.click(stakeButton); expect(openTabSpy).toHaveBeenCalledTimes(1); await waitFor(() => diff --git a/ui/components/multichain/token-list-item/token-list-item.js b/ui/components/multichain/token-list-item/token-list-item.tsx similarity index 83% rename from ui/components/multichain/token-list-item/token-list-item.js rename to ui/components/multichain/token-list-item/token-list-item.tsx index f28a53bcd075..c227a1806245 100644 --- a/ui/components/multichain/token-list-item/token-list-item.js +++ b/ui/components/multichain/token-list-item/token-list-item.tsx @@ -1,5 +1,4 @@ import React, { useContext, useState } from 'react'; -import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; import classnames from 'classnames'; @@ -24,16 +23,19 @@ import { BadgeWrapper, Box, ButtonIcon, + ButtonIconSize, ButtonSecondary, Icon, IconName, IconSize, Modal, + ModalBody, + ModalContent, + ModalFooter, + ModalHeader, ModalOverlay, Text, } from '../../component-library'; -import { ModalContent } from '../../component-library/modal-content/deprecated'; -import { ModalHeader } from '../../component-library/modal-header/deprecated'; import { getMetaMetricsId, getTestNetworkBackgroundColor, @@ -63,6 +65,22 @@ import { getProviderConfig } from '../../../ducks/metamask/metamask'; import { getPortfolioUrl } from '../../../helpers/utils/portfolio'; import { PercentageChange } from './price/percentage-change/percentage-change'; +type TokenListItemProps = { + className?: string; + onClick?: (arg?: string) => void; + tokenSymbol?: string; + tokenImage: string; + primary?: string; + secondary?: string | null; + title: string; + tooltipText?: string; + isOriginalTokenSymbol?: boolean | null; + isNativeCurrency?: boolean; + isStakeable?: boolean; + address?: string | null; + showPercentage?: boolean; +}; + export const TokenListItem = ({ className, onClick, @@ -77,7 +95,7 @@ export const TokenListItem = ({ isStakeable = false, address = null, showPercentage = false, -}) => { +}: TokenListItemProps) => { const t = useI18nContext(); const isEvm = useSelector(getMultichainIsEvm); const trackEvent = useContext(MetaMetricsContext); @@ -114,8 +132,9 @@ export const TokenListItem = ({ const tokensMarketData = useSelector(getTokensMarketData); - const tokenPercentageChange = - tokensMarketData?.[address]?.pricePercentChange1d; + const tokenPercentageChange = address + ? tokensMarketData?.[address]?.pricePercentChange1d + : null; const tokenTitle = getTokenTitle(); const tokenMainTitleToDisplay = showPercentage ? tokenTitle : tokenSymbol; @@ -129,8 +148,8 @@ export const TokenListItem = ({ paddingInline={0} paddingInlineStart={1} paddingInlineEnd={1} - tabIndex="0" - onClick={(e) => { + tabIndex={0} + onClick={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); const url = getPortfolioUrl( @@ -177,7 +196,7 @@ export const TokenListItem = ({ return ( { + onClick: (e: React.MouseEvent) => { e.preventDefault(); if (showScamWarningModal) { @@ -224,7 +243,7 @@ export const TokenListItem = ({ badge={ )} @@ -321,15 +344,18 @@ export const TokenListItem = ({ > { + onClick={( + e: React.MouseEvent, + ) => { e.preventDefault(); e.stopPropagation(); setShowScamWarningModal(true); }} color={IconColor.errorDefault} - size={IconSize.Md} + size={ButtonIconSize.Md} backgroundColor={BackgroundColor.transparent} data-testid="scam-warning" + ariaLabel={''} /> {isEvm && showScamWarningModal ? ( - + setShowScamWarningModal(false)}> - setShowScamWarningModal(false)}> - {t('nativeTokenScamWarningTitle')} - - - {t('nativeTokenScamWarningDescription', [tokenSymbol])} - - + {t('nativeTokenScamWarningTitle')} + + + {t('nativeTokenScamWarningDescription', [tokenSymbol])} + + + { dispatch( @@ -396,72 +422,17 @@ export const TokenListItem = ({ if (isFullScreen) { history.push(NETWORKS_ROUTE); } else { - global.platform.openExtensionInBrowser(NETWORKS_ROUTE); + global.platform.openExtensionInBrowser?.(NETWORKS_ROUTE); } }} block > {t('nativeTokenScamWarningConversion')} - + ) : null} ); }; - -TokenListItem.propTypes = { - /** - * An additional className to apply to the TokenList. - */ - className: PropTypes.string, - /** - * The onClick handler to be passed to the TokenListItem 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, - /** - * tooltipText represents the text to show in the tooltip when hovering over the token - */ - tooltipText: PropTypes.string, - /** - * tokenImage represents 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, - /** - * isOriginalTokenSymbol represents a boolean value to check if the token symbol is original or not - */ - isOriginalTokenSymbol: PropTypes.bool, - /** - * isNativeCurrency represents if this item is the native currency - */ - isNativeCurrency: PropTypes.bool, - /** - * isStakeable represents if this item is stakeable - */ - isStakeable: PropTypes.bool, - /** - * address represents the token address - */ - address: PropTypes.string, - /** - * showPercentage represents if the increase decrease percentage will be hidden - */ - showPercentage: PropTypes.bool, -}; diff --git a/ui/hooks/useHideFiatForTestnet.test.ts b/ui/hooks/useHideFiatForTestnet.test.ts deleted file mode 100644 index cdc4905c74c4..000000000000 --- a/ui/hooks/useHideFiatForTestnet.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { renderHook } from '@testing-library/react-hooks'; -import { getShowFiatInTestnets, getCurrentChainId } from '../selectors'; -import { TEST_NETWORK_IDS, CHAIN_IDS } from '../../shared/constants/network'; -import { useHideFiatForTestnet } from './useHideFiatForTestnet'; - -jest.mock('react-redux', () => ({ - useSelector: jest.fn().mockImplementation((selector) => selector()), -})); - -jest.mock('../selectors', () => ({ - getShowFiatInTestnets: jest.fn(), - getCurrentChainId: jest.fn(), -})); - -describe('useHideFiatForTestnet', () => { - const mockGetShowFiatInTestnets = jest.mocked(getShowFiatInTestnets); - const mockGetCurrentChainId = jest.mocked(getCurrentChainId); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('utilizes the specified chain id', () => { - mockGetShowFiatInTestnets.mockReturnValue(false); - mockGetCurrentChainId.mockReturnValue(TEST_NETWORK_IDS[0]); - - const { result } = renderHook(() => - useHideFiatForTestnet(CHAIN_IDS.MAINNET), - ); - - expect(result.current).toBe(false); - }); - - it('returns true if current network is a testnet and showFiatInTestnets is false', () => { - mockGetShowFiatInTestnets.mockReturnValue(false); - mockGetCurrentChainId.mockReturnValue(TEST_NETWORK_IDS[0]); - - const { result } = renderHook(() => useHideFiatForTestnet()); - - expect(result.current).toBe(true); - }); - - it('returns false if current network is a testnet and showFiatInTestnets is true', () => { - mockGetShowFiatInTestnets.mockReturnValue(true); - mockGetCurrentChainId.mockReturnValue(TEST_NETWORK_IDS[0]); - - const { result } = renderHook(() => useHideFiatForTestnet()); - - expect(result.current).toBe(false); - }); - - it('returns false if current network is not a testnet', () => { - mockGetShowFiatInTestnets.mockReturnValue(false); - mockGetCurrentChainId.mockReturnValue('1'); - - const { result } = renderHook(() => useHideFiatForTestnet()); - - expect(result.current).toBe(false); - }); - - it('returns false if current network is not a testnet but showFiatInTestnets is true', () => { - mockGetShowFiatInTestnets.mockReturnValue(true); - mockGetCurrentChainId.mockReturnValue('1'); - - const { result } = renderHook(() => useHideFiatForTestnet()); - - expect(result.current).toBe(false); - }); -}); diff --git a/ui/hooks/useHideFiatForTestnet.ts b/ui/hooks/useHideFiatForTestnet.ts deleted file mode 100644 index b01b20513550..000000000000 --- a/ui/hooks/useHideFiatForTestnet.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { useSelector } from 'react-redux'; -import type { Hex } from '@metamask/utils'; -import { getShowFiatInTestnets, getCurrentChainId } from '../selectors'; -import { TEST_NETWORK_IDS } from '../../shared/constants/network'; - -/** - * Returns true if the fiat value should be hidden for testnet networks. - * - * @param providedChainId - * @returns boolean - */ -export const useHideFiatForTestnet = (providedChainId?: Hex): boolean => { - const showFiatInTestnets = useSelector(getShowFiatInTestnets); - const currentChainId = useSelector(getCurrentChainId); - const chainId = providedChainId ?? currentChainId; - return TEST_NETWORK_IDS.includes(chainId) && !showFiatInTestnets; -}; diff --git a/ui/hooks/useUserPreferencedCurrency.test.js b/ui/hooks/useUserPreferencedCurrency.test.js index c4818d9e980d..12785d44b5ff 100644 --- a/ui/hooks/useUserPreferencedCurrency.test.js +++ b/ui/hooks/useUserPreferencedCurrency.test.js @@ -123,7 +123,7 @@ const renderUseUserPreferencedCurrency = (state, value, restProps) => { ...mockState.metamask, completedOnboarding: true, ...mockNetworkState({ - chainId: state.showFiat ? CHAIN_IDS.MAINNET : CHAIN_IDS.LOCALHOST, + chainId: state.showFiat ? CHAIN_IDS.MAINNET : CHAIN_IDS.SEPOLIA, ticker: state?.nativeCurrency, }), currentCurrency: state.currentCurrency, diff --git a/ui/pages/confirmations/components/confirm-gas-display/__snapshots__/confirm-gas-display.test.js.snap b/ui/pages/confirmations/components/confirm-gas-display/__snapshots__/confirm-gas-display.test.js.snap index c6d58247c683..b13f1f6d31e9 100644 --- a/ui/pages/confirmations/components/confirm-gas-display/__snapshots__/confirm-gas-display.test.js.snap +++ b/ui/pages/confirmations/components/confirm-gas-display/__snapshots__/confirm-gas-display.test.js.snap @@ -37,16 +37,6 @@ exports[`ConfirmGasDisplay should match snapshot 1`] = ` class="mm-box mm-text mm-text--inherit mm-box--color-primary-default" /> -
- - 0.001197 - -
- + {shouldShowFiat && ( + + )} ); }, @@ -75,20 +78,22 @@ export default function FeeDetailsComponent({ return ( - + {shouldShowFiat && ( + + )} ); }, @@ -168,9 +173,7 @@ export default function FeeDetailsComponent({ {t('layer1Fees')} } - detailText={ - useCurrencyRateCheck && renderTotalDetailText(layer1GasFee) - } + detailText={shouldShowFiat && renderTotalDetailText(layer1GasFee)} detailTotal={renderTotalDetailValue(layer1GasFee)} /> )} @@ -178,8 +181,7 @@ export default function FeeDetailsComponent({ diff --git a/ui/pages/confirmations/components/gas-details-item/gas-details-item.js b/ui/pages/confirmations/components/gas-details-item/gas-details-item.js index ef02aadbfa33..90ccbb09ce23 100644 --- a/ui/pages/confirmations/components/gas-details-item/gas-details-item.js +++ b/ui/pages/confirmations/components/gas-details-item/gas-details-item.js @@ -18,8 +18,8 @@ import { PRIMARY, SECONDARY } from '../../../../helpers/constants/common'; import { PriorityLevels } from '../../../../../shared/constants/gas'; import { getPreferences, + getShouldShowFiat, getTxData, - getUseCurrencyRateCheck, transactionFeeSelector, } from '../../../../selectors'; import { getCurrentDraftTransaction } from '../../../../ducks/send'; @@ -44,12 +44,14 @@ const GasDetailsItem = ({ userAcknowledgedGasMissing = false, }) => { const t = useI18nContext(); + const shouldShowFiat = useSelector(getShouldShowFiat); const txData = useSelector(getTxData); const { layer1GasFee } = txData; const draftTransaction = useSelector(getCurrentDraftTransaction); const transactionData = useDraftTransactionWithTxParams(); + const { hexMinimumTransactionFee: draftHexMinimumTransactionFee, hexMaximumTransactionFee: draftHexMaximumTransactionFee, @@ -67,8 +69,6 @@ const GasDetailsItem = ({ } = useGasFeeContext(); const { useNativeCurrencyAsPrimaryCurrency } = useSelector(getPreferences); - - const useCurrencyRateCheck = useSelector(getUseCurrencyRateCheck); const getTransactionFeeTotal = useMemo(() => { if (layer1GasFee) { return sumHexes(hexMinimumTransactionFee, layer1GasFee); @@ -137,7 +137,7 @@ const GasDetailsItem = ({ - {useCurrencyRateCheck && ( + {shouldShowFiat && ( = ({ fiatAmount, shorten = false }) => { - const hideFiatForTestnet = useHideFiatForTestnet(); + const shouldShowFiat = useSelector(getShouldShowFiat); const fiatFormatter = useFiatFormatter(); - if (hideFiatForTestnet) { + if (!shouldShowFiat) { return null; } @@ -76,12 +77,12 @@ export const IndividualFiatDisplay: React.FC<{ export const TotalFiatDisplay: React.FC<{ fiatAmounts: FiatAmount[]; }> = ({ fiatAmounts }) => { - const hideFiatForTestnet = useHideFiatForTestnet(); + const shouldShowFiat = useSelector(getShouldShowFiat); const t = useI18nContext(); const fiatFormatter = useFiatFormatter(); const totalFiat = calculateTotalFiat(fiatAmounts); - if (hideFiatForTestnet) { + if (!shouldShowFiat) { return null; } diff --git a/ui/pages/confirmations/confirm-approve/confirm-approve-content/confirm-approve-content.component.test.js b/ui/pages/confirmations/confirm-approve/confirm-approve-content/confirm-approve-content.component.test.js index 916da9c1c58b..2abec9ef4c13 100644 --- a/ui/pages/confirmations/confirm-approve/confirm-approve-content/confirm-approve-content.component.test.js +++ b/ui/pages/confirmations/confirm-approve/confirm-approve-content/confirm-approve-content.component.test.js @@ -14,6 +14,7 @@ const renderComponent = (props) => { preferences: { useNativeCurrencyAsPrimaryCurrency: true, }, + currencyRates: {}, }, }); return renderWithProvider(, store); diff --git a/ui/pages/confirmations/confirm-send-ether/__snapshots__/confirm-send-ether.test.js.snap b/ui/pages/confirmations/confirm-send-ether/__snapshots__/confirm-send-ether.test.js.snap index 42f9a93c1d4f..1da44ad87146 100644 --- a/ui/pages/confirmations/confirm-send-ether/__snapshots__/confirm-send-ether.test.js.snap +++ b/ui/pages/confirmations/confirm-send-ether/__snapshots__/confirm-send-ether.test.js.snap @@ -455,16 +455,6 @@ exports[`ConfirmSendEther should render correct information for for confirm send class="mm-box mm-text mm-text--inherit mm-box--color-primary-default" /> -
- - 0.000021 - -