diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 12df50e257d7..3bfd303e5dd0 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -633,9 +633,15 @@ "asset": { "message": "Asset" }, + "assetMultipleNFTsBalance": { + "message": "$1 NFTs" + }, "assetOptions": { "message": "Asset options" }, + "assetSingleNFTBalance": { + "message": "$1 NFT" + }, "assets": { "message": "Assets" }, @@ -883,6 +889,9 @@ "bridgeExplorerLinkViewOn": { "message": "View on $1" }, + "bridgeFetchNewQuotes": { + "message": "Fetch a new one?" + }, "bridgeFrom": { "message": "Bridge from" }, @@ -897,7 +906,7 @@ "message": "Net cost" }, "bridgeQuoteExpired": { - "message": "Quotes expired - please request again" + "message": "Your quote timed out." }, "bridgeSelectNetwork": { "message": "Select network" @@ -6825,7 +6834,7 @@ "message": "Welcome back!" }, "welcomeExploreDescription": { - "message": "Store, send and spend crypto currencies and assets." + "message": "Store, send, and spend crypto currencies and assets." }, "welcomeExploreTitle": { "message": "Explore decentralized apps" diff --git a/app/scripts/constants/sentry-state.ts b/app/scripts/constants/sentry-state.ts index 68926c02dc89..ab53ffb3f22d 100644 --- a/app/scripts/constants/sentry-state.ts +++ b/app/scripts/constants/sentry-state.ts @@ -104,10 +104,10 @@ export const SENTRY_BACKGROUND_STATE = { }, destTokens: {}, destTopAssets: [], - destTokensLoadingStatus: false, + destTokensLoadingStatus: true, srcTokens: {}, srcTopAssets: [], - srcTokensLoadingStatus: false, + srcTokensLoadingStatus: true, quoteRequest: { walletAddress: false, srcTokenAddress: true, diff --git a/test/e2e/flask/btc/btc-account-overview.spec.ts b/test/e2e/flask/btc/btc-account-overview.spec.ts index 418c9d736078..93ff6ef07ad3 100644 --- a/test/e2e/flask/btc/btc-account-overview.spec.ts +++ b/test/e2e/flask/btc/btc-account-overview.spec.ts @@ -1,63 +1,24 @@ +import { strict as assert } from 'assert'; import { Suite } from 'mocha'; import { DEFAULT_BTC_BALANCE } from '../../constants'; +import BitcoinHomepage from '../../page-objects/pages/home/bitcoin-homepage'; import { withBtcAccountSnap } from './common-btc'; describe('BTC Account - Overview', function (this: Suite) { - it('has portfolio button enabled for BTC accounts', async function () { + it('has balance displayed and has portfolio button enabled for BTC accounts', async function () { await withBtcAccountSnap( { title: this.test?.fullTitle() }, async (driver) => { - await driver.findElement({ - css: '[data-testid="account-menu-icon"]', - text: 'Bitcoin Account', - }); - - await driver.waitForSelector({ - text: 'Send', - tag: 'button', - css: '[data-testid="coin-overview-send"]', - }); - - await driver.waitForSelector({ - text: 'Swap', - tag: 'button', - css: '[disabled]', - }); - - await driver.waitForSelector({ - text: 'Bridge', - tag: 'button', - css: '[disabled]', - }); - - // buy sell button - await driver.findClickableElement('[data-testid="coin-overview-buy"]'); - - // receive button - await driver.findClickableElement( - '[data-testid="coin-overview-receive"]', + const homePage = new BitcoinHomepage(driver); + await homePage.check_pageIsLoaded(); + await homePage.headerNavbar.check_accountLabel('Bitcoin Account'); + await homePage.check_isExpectedBitcoinBalanceDisplayed( + DEFAULT_BTC_BALANCE, ); - }, - ); - }); - - it('has balance', async function () { - await withBtcAccountSnap( - { title: this.test?.fullTitle() }, - async (driver) => { - await driver.waitForSelector({ - testId: 'account-value-and-suffix', - text: `${DEFAULT_BTC_BALANCE}`, - }); - await driver.waitForSelector({ - css: '.currency-display-component__suffix', - text: 'BTC', - }); - - await driver.waitForSelector({ - tag: 'p', - text: `${DEFAULT_BTC_BALANCE} BTC`, - }); + assert.equal(await homePage.check_isBridgeButtonEnabled(), false); + assert.equal(await homePage.check_isSwapButtonEnabled(), false); + assert.equal(await homePage.check_isBuySellButtonEnabled(), true); + assert.equal(await homePage.check_isReceiveButtonEnabled(), true); }, ); }); diff --git a/test/e2e/flask/btc/btc-dapp-connection.spec.ts b/test/e2e/flask/btc/btc-dapp-connection.spec.ts index 2eb33a4b7d74..2a9b413f60b0 100644 --- a/test/e2e/flask/btc/btc-dapp-connection.spec.ts +++ b/test/e2e/flask/btc/btc-dapp-connection.spec.ts @@ -1,5 +1,6 @@ import { Suite } from 'mocha'; -import { openDapp, WINDOW_TITLES } from '../../helpers'; +import BitcoinHomepage from '../../page-objects/pages/home/bitcoin-homepage'; +import TestDapp from '../../page-objects/pages/test-dapp'; import { withBtcAccountSnap } from './common-btc'; describe('BTC Account - Dapp Connection', function (this: Suite) { @@ -7,14 +8,16 @@ describe('BTC Account - Dapp Connection', function (this: Suite) { await withBtcAccountSnap( { title: this.test?.fullTitle() }, async (driver) => { - await openDapp(driver); - await driver.clickElement('#connectButton'); - await driver.waitUntilXWindowHandles(3); - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + const homePage = new BitcoinHomepage(driver); + await homePage.check_pageIsLoaded(); + await homePage.headerNavbar.check_accountLabel('Bitcoin Account'); - await driver.assertElementNotPresent( - '[data-testid="choose-account-list-1"]', - ); + const testDapp = new TestDapp(driver); + await testDapp.openTestDappPage(); + await testDapp.check_pageIsLoaded(); + await testDapp.connectAccount({ + connectAccountButtonEnabled: false, + }); }, ); }); diff --git a/test/e2e/flask/btc/btc-experimental-settings.spec.ts b/test/e2e/flask/btc/btc-experimental-settings.spec.ts index 45bc7d0d1701..10a183bf87bb 100644 --- a/test/e2e/flask/btc/btc-experimental-settings.spec.ts +++ b/test/e2e/flask/btc/btc-experimental-settings.spec.ts @@ -1,47 +1,45 @@ import { Suite } from 'mocha'; - -import messages from '../../../../app/_locales/en/messages.json'; import FixtureBuilder from '../../fixture-builder'; -import { - defaultGanacheOptions, - unlockWallet, - withFixtures, -} from '../../helpers'; +import { withFixtures } from '../../helpers'; import { Driver } from '../../webdriver/driver'; +import AccountListPage from '../../page-objects/pages/account-list-page'; +import ExperimentalSettings from '../../page-objects/pages/settings/experimental-settings'; +import HomePage from '../../page-objects/pages/home/homepage'; +import HeaderNavbar from '../../page-objects/pages/header-navbar'; +import SettingsPage from '../../page-objects/pages/settings/settings-page'; +import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; describe('BTC Experimental Settings', function (this: Suite) { it('will show `Add a new Bitcoin account (Beta)` option when setting is enabled', async function () { await withFixtures( { fixtures: new FixtureBuilder().build(), - ganacheOptions: defaultGanacheOptions, title: this.test?.fullTitle(), }, async ({ driver }: { driver: Driver }) => { - await unlockWallet(driver); - await driver.clickElement( - '[data-testid="account-options-menu-button"]', - ); - await driver.clickElement({ text: 'Settings', tag: 'div' }); - await driver.clickElement({ text: 'Experimental', tag: 'div' }); + await loginWithBalanceValidation(driver); - await driver.waitForSelector({ - text: messages.bitcoinSupportToggleTitle.message, - tag: 'span', - }); + // go to experimental settings page and enable add new Bitcoin account toggle + const homePage = new HomePage(driver); + await homePage.check_pageIsLoaded(); + await homePage.check_expectedBalanceIsDisplayed(); - await driver.clickElement('[data-testid="bitcoin-support-toggle-div"]'); + await new HeaderNavbar(driver).openSettingsPage(); + const settingsPage = new SettingsPage(driver); + await settingsPage.check_pageIsLoaded(); + await settingsPage.goToExperimentalSettings(); - await driver.clickElement('button[aria-label="Close"]'); + const experimentalSettings = new ExperimentalSettings(driver); + await experimentalSettings.check_pageIsLoaded(); + await experimentalSettings.toggleBitcoinAccount(); + await settingsPage.closeSettingsPage(); - await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.clickElement( - '[data-testid="multichain-account-menu-popover-action-button"]', - ); - await driver.waitForSelector({ - text: messages.addNewBitcoinAccount.message, - tag: 'button', - }); + // check add new Bitcoin account button is displayed + await homePage.check_pageIsLoaded(); + await new HeaderNavbar(driver).openAccountMenu(); + const accountListPage = new AccountListPage(driver); + await accountListPage.check_pageIsLoaded(); + await accountListPage.check_addBitcoinAccountAvailable(true); }, ); }); diff --git a/test/e2e/page-objects/pages/account-list-page.ts b/test/e2e/page-objects/pages/account-list-page.ts index 981955eb19cd..50eb70ce553c 100644 --- a/test/e2e/page-objects/pages/account-list-page.ts +++ b/test/e2e/page-objects/pages/account-list-page.ts @@ -374,6 +374,22 @@ class AccountListPage { }); } + async check_addBitcoinAccountAvailable( + expectedAvailability: boolean, + ): Promise { + console.log( + `Check add bitcoin account button is ${ + expectedAvailability ? 'displayed ' : 'not displayed' + }`, + ); + await this.openAddAccountModal(); + if (expectedAvailability) { + await this.driver.waitForSelector(this.addBtcAccountButton); + } else { + await this.driver.assertElementNotPresent(this.addBtcAccountButton); + } + } + async openAccountOptionsMenu(): Promise { console.log(`Open account option menu`); await this.driver.waitForSelector(this.accountListItem); diff --git a/test/e2e/page-objects/pages/header-navbar.ts b/test/e2e/page-objects/pages/header-navbar.ts index 65d4cbd48358..df45ecd0277d 100644 --- a/test/e2e/page-objects/pages/header-navbar.ts +++ b/test/e2e/page-objects/pages/header-navbar.ts @@ -1,7 +1,7 @@ import { Driver } from '../../webdriver/driver'; class HeaderNavbar { - private driver: Driver; + protected driver: Driver; private readonly accountMenuButton = '[data-testid="account-menu-icon"]'; diff --git a/test/e2e/page-objects/pages/home/bitcoin-homepage.ts b/test/e2e/page-objects/pages/home/bitcoin-homepage.ts new file mode 100644 index 000000000000..19b235636405 --- /dev/null +++ b/test/e2e/page-objects/pages/home/bitcoin-homepage.ts @@ -0,0 +1,112 @@ +import HomePage from './homepage'; + +class BitcoinHomepage extends HomePage { + protected readonly balance = + '[data-testid="coin-overview__primary-currency"]'; + + private readonly bridgeButton = { + text: 'Bridge', + tag: 'button', + }; + + private readonly buySellButton = '[data-testid="coin-overview-buy"]'; + + private readonly receiveButton = '[data-testid="coin-overview-receive"]'; + + protected readonly sendButton = '[data-testid="coin-overview-send"]'; + + private readonly swapButton = { + text: 'Swap', + tag: 'button', + }; + + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForMultipleSelectors([ + this.sendButton, + this.buySellButton, + this.receiveButton, + ]); + } catch (e) { + console.log('Timeout while waiting for bitcoin homepage to be loaded', e); + throw e; + } + console.log('Bitcoin homepage is loaded'); + } + + /** + * Checks if the bridge button is enabled on bitcoin account homepage. + * + */ + async check_isBridgeButtonEnabled(): Promise { + try { + await this.driver.findClickableElement(this.bridgeButton, 1000); + } catch (e) { + console.log('Bridge button not enabled', e); + return false; + } + console.log('Bridge button is enabled'); + return true; + } + + /** + * Checks if the buy/sell button is enabled on bitcoin account homepage. + */ + async check_isBuySellButtonEnabled(): Promise { + try { + await this.driver.findClickableElement(this.buySellButton, 1000); + } catch (e) { + console.log('Buy/Sell button not enabled', e); + return false; + } + console.log('Buy/Sell button is enabled'); + return true; + } + + /** + * Checks if the expected bitcoin balance is displayed on homepage. + * + * @param expectedBalance - The expected bitcoin balance to be displayed. Defaults to '0'. + */ + async check_isExpectedBitcoinBalanceDisplayed( + expectedBalance: number = 0, + ): Promise { + console.log( + `Check if expected bitcoin balance is displayed: ${expectedBalance} BTC`, + ); + await this.driver.waitForSelector({ + css: this.balance, + text: `${expectedBalance}BTC`, + }); + } + + /** + * Checks if the receive button is enabled on bitcoin account homepage. + */ + async check_isReceiveButtonEnabled(): Promise { + try { + await this.driver.findClickableElement(this.receiveButton, 1000); + } catch (e) { + console.log('Receive button not enabled', e); + return false; + } + console.log('Receive button is enabled'); + return true; + } + + /** + * Checks if the swap button is enabled on bitcoin account homepage. + */ + async check_isSwapButtonEnabled(): Promise { + try { + await this.driver.findClickableElement(this.swapButton, 1000); + } catch (e) { + console.log('Swap button not enabled', e); + return false; + } + console.log('Swap button is enabled'); + return true; + } +} + +export default BitcoinHomepage; diff --git a/test/e2e/page-objects/pages/home/homepage.ts b/test/e2e/page-objects/pages/home/homepage.ts index 1d44ac70c4b7..b4b79846fb06 100644 --- a/test/e2e/page-objects/pages/home/homepage.ts +++ b/test/e2e/page-objects/pages/home/homepage.ts @@ -4,7 +4,7 @@ import { getCleanAppState } from '../../../helpers'; import HeaderNavbar from '../header-navbar'; class HomePage { - private driver: Driver; + protected driver: Driver; public headerNavbar: HeaderNavbar; @@ -12,7 +12,8 @@ class HomePage { testId: 'account-overview__activity-tab', }; - private readonly balance = '[data-testid="eth-overview__primary-currency"]'; + protected readonly balance: string = + '[data-testid="eth-overview__primary-currency"]'; private readonly basicFunctionalityOffWarningMessage = { text: 'Basic functionality is off', @@ -48,9 +49,7 @@ class HomePage { testId: 'refreshList', }; - private readonly sendButton = { - testId: 'eth-overview-send', - }; + protected readonly sendButton: string = '[data-testid="eth-overview-send"]'; private readonly tokensTab = { testId: 'account-overview__asset-tab', diff --git a/test/e2e/page-objects/pages/settings/experimental-settings.ts b/test/e2e/page-objects/pages/settings/experimental-settings.ts index c3f81d99bf23..f551db6d47c5 100644 --- a/test/e2e/page-objects/pages/settings/experimental-settings.ts +++ b/test/e2e/page-objects/pages/settings/experimental-settings.ts @@ -1,4 +1,5 @@ import { Driver } from '../../../webdriver/driver'; +import messages from '../../../../../app/_locales/en/messages.json'; class ExperimentalSettings { private readonly driver: Driver; @@ -7,6 +8,9 @@ class ExperimentalSettings { private readonly addAccountSnapToggle = '[data-testid="add-account-snap-toggle-div"]'; + private readonly addBitcoinAccountToggle = + '[data-testid="bitcoin-support-toggle-div"]'; + private readonly experimentalPageTitle = { text: 'Experimental', tag: 'h4', @@ -35,6 +39,15 @@ class ExperimentalSettings { console.log('Experimental Settings page is loaded'); } + async toggleBitcoinAccount(): Promise { + console.log('Toggle Add new Bitcoin account on experimental setting page'); + await this.driver.waitForSelector({ + text: messages.bitcoinSupportToggleTitle.message, + tag: 'span', + }); + await this.driver.clickElement(this.addBitcoinAccountToggle); + } + async toggleAddAccountSnap(): Promise { console.log('Toggle Add Account Snap on experimental setting page'); await this.driver.clickElement(this.addAccountSnapToggle); diff --git a/test/e2e/page-objects/pages/test-dapp.ts b/test/e2e/page-objects/pages/test-dapp.ts index 09c450c7a49a..fe29bd86c143 100644 --- a/test/e2e/page-objects/pages/test-dapp.ts +++ b/test/e2e/page-objects/pages/test-dapp.ts @@ -286,9 +286,17 @@ class TestDapp { /** * Connect account to test dapp. * - * @param publicAddress - The public address to connect to test dapp. + * @param options - Options for connecting account to test dapp. + * @param [options.connectAccountButtonEnabled] - Indicates if the connect account button should be enabled. + * @param options.publicAddress - The public address to connect to test dapp. */ - async connectAccount(publicAddress: string) { + async connectAccount({ + connectAccountButtonEnabled = true, + publicAddress, + }: { + connectAccountButtonEnabled?: boolean; + publicAddress?: string; + }) { console.log('Connect account to test dapp'); await this.driver.clickElement(this.connectAccountButton); await this.confirmConnectAccountModal(); diff --git a/test/e2e/tests/account/snap-account-signatures-and-disconnects.spec.ts b/test/e2e/tests/account/snap-account-signatures-and-disconnects.spec.ts index 7e355302212a..d3c177ff7fdd 100644 --- a/test/e2e/tests/account/snap-account-signatures-and-disconnects.spec.ts +++ b/test/e2e/tests/account/snap-account-signatures-and-disconnects.spec.ts @@ -56,7 +56,9 @@ describe('Snap Account Signatures and Disconnects @no-mmi', function (this: Suit // Disconnect from Test Dapp and reconnect to Test Dapp await testDapp.disconnectAccount(newPublicKey); - await testDapp.connectAccount(newPublicKey); + await testDapp.connectAccount({ + publicAddress: newPublicKey, + }); // SignTypedDataV4 with Test Dapp await signTypedDataV4WithSnapAccount(driver, newPublicKey, false, true); diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json index b00f37993b78..cae1a6ae8951 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json @@ -76,8 +76,6 @@ "srcTokens": {}, "srcTopAssets": {}, "destTokens": {}, - "destTokensLoadingStatus": "undefined", - "srcTokensLoadingStatus": "undefined", "destTopAssets": {}, "quoteRequest": { "srcTokenAddress": "0x0000000000000000000000000000000000000000", diff --git a/test/e2e/tests/multichain/all-permissions-page.spec.ts b/test/e2e/tests/multichain/all-permissions-page.spec.ts index 756bae735f15..eca4052fdbf7 100644 --- a/test/e2e/tests/multichain/all-permissions-page.spec.ts +++ b/test/e2e/tests/multichain/all-permissions-page.spec.ts @@ -21,7 +21,9 @@ describe('Permissions Page', function () { await loginWithoutBalanceValidation(driver); const testDapp = new TestDapp(driver); await testDapp.openTestDappPage(); - await testDapp.connectAccount(DEFAULT_FIXTURE_ACCOUNT); + await testDapp.connectAccount({ + publicAddress: DEFAULT_FIXTURE_ACCOUNT, + }); // switch to extension window and check the site permissions await driver.switchToWindowWithTitle( 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 3774af82ae1c..16ba368e16ce 100644 --- a/test/e2e/tests/tokens/custom-token-send-transfer.spec.js +++ b/test/e2e/tests/tokens/custom-token-send-transfer.spec.js @@ -158,14 +158,10 @@ describe('Transfer custom tokens @no-mmi', function () { // check token amount is correct after transaction await clickNestedButton(driver, 'Tokens'); - const tokenAmount = await driver.findElement( - { - css: '[data-testid="multichain-token-list-item-value"]', - text: '8.5 TST', - }, - { timeout: 10000 }, - ); - assert.ok(tokenAmount, 'Token amount is not correct'); + await driver.waitForSelector({ + css: '[data-testid="multichain-token-list-item-value"]', + text: '8.5 TST', + }); }, ); }); @@ -223,14 +219,10 @@ describe('Transfer custom tokens @no-mmi', function () { // check token amount is correct after transaction await clickNestedButton(driver, 'Tokens'); - const tokenAmount = await driver.findElement( - { - css: '[data-testid="multichain-token-list-item-value"]', - text: '8.5 TST', - }, - { timeout: 10000 }, - ); - assert.ok(tokenAmount, 'Token amount is not correct'); + await driver.waitForSelector({ + css: '[data-testid="multichain-token-list-item-value"]', + text: '8.5 TST', + }); }, ); }); @@ -346,14 +338,10 @@ describe('Transfer custom tokens @no-mmi', function () { // check token amount is correct after transaction await clickNestedButton(driver, 'Tokens'); - const tokenAmount = await driver.findElement( - { - css: '[data-testid="multichain-token-list-item-value"]', - text: '8.5 TST', - }, - { timeout: 10000 }, - ); - assert.ok(tokenAmount, 'Token amount is not correct'); + await driver.waitForSelector({ + css: '[data-testid="multichain-token-list-item-value"]', + text: '8.5 TST', + }); }, ); }); @@ -410,14 +398,10 @@ describe('Transfer custom tokens @no-mmi', function () { // check token amount is correct after transaction await clickNestedButton(driver, 'Tokens'); - const tokenAmount = await driver.findElement( - { - css: '[data-testid="multichain-token-list-item-value"]', - text: '8.5 TST', - }, - { timeout: 10000 }, - ); - assert.ok(tokenAmount, 'Token amount is not correct'); + await driver.waitForSelector({ + css: '[data-testid="multichain-token-list-item-value"]', + text: '8.5 TST', + }); }, ); }); diff --git a/test/e2e/tests/tokens/nft/send-nft.spec.js b/test/e2e/tests/tokens/nft/send-nft.spec.js index 13cb310f1416..af0d0d596337 100644 --- a/test/e2e/tests/tokens/nft/send-nft.spec.js +++ b/test/e2e/tests/tokens/nft/send-nft.spec.js @@ -177,7 +177,7 @@ describe('Send NFT', function () { await driver.fill('input[placeholder="0"]', '0'); assert.ok( await driver.findElement({ - text: '1 token. Cannot send negative or zero amounts of asset.', + text: '1 NFT. Cannot send negative or zero amounts of asset.', tag: 'p', }), ); diff --git a/test/e2e/webdriver/driver.js b/test/e2e/webdriver/driver.js index b0a335eb0d64..c16ef3999490 100644 --- a/test/e2e/webdriver/driver.js +++ b/test/e2e/webdriver/driver.js @@ -499,13 +499,14 @@ class Driver { * and returns a reference to the first matching element. * * @param {string | object} rawLocator - Element locator + * @param {number} timeout - Timeout in milliseconds * @returns {Promise} A promise that resolves to the found element. */ - async findElement(rawLocator) { + async findElement(rawLocator, timeout = this.timeout) { const locator = this.buildLocator(rawLocator); const element = await this.driver.wait( until.elementLocated(locator), - this.timeout, + timeout, ); return wrapElementWithAPI(element, this); } @@ -540,13 +541,14 @@ class Driver { * Finds a clickable element on the page using the given locator. * * @param {string | object} rawLocator - Element locator + * @param {number} timeout - Timeout in milliseconds * @returns {Promise} A promise that resolves to the found clickable element. */ - async findClickableElement(rawLocator) { - const element = await this.findElement(rawLocator); + async findClickableElement(rawLocator, timeout = this.timeout) { + const element = await this.findElement(rawLocator, timeout); await Promise.all([ - this.driver.wait(until.elementIsVisible(element), this.timeout), - this.driver.wait(until.elementIsEnabled(element), this.timeout), + this.driver.wait(until.elementIsVisible(element), timeout), + this.driver.wait(until.elementIsEnabled(element), timeout), ]); return wrapElementWithAPI(element, this); } diff --git a/ui/components/app/assets/asset-list/asset-list-control-bar/asset-list-control-bar.tsx b/ui/components/app/assets/asset-list/asset-list-control-bar/asset-list-control-bar.tsx index 348fd82fbdaa..c61815a9ab0a 100644 --- a/ui/components/app/assets/asset-list/asset-list-control-bar/asset-list-control-bar.tsx +++ b/ui/components/app/assets/asset-list/asset-list-control-bar/asset-list-control-bar.tsx @@ -3,6 +3,7 @@ import { useDispatch, useSelector } from 'react-redux'; import { getCurrentNetwork, getIsTokenNetworkFilterEqualCurrentNetwork, + getSelectedInternalAccount, getTokenNetworkFilter, } from '../../../../../selectors'; import { getNetworkConfigurationsByChainId } from '../../../../../../shared/modules/selectors/networks'; @@ -49,6 +50,8 @@ import { showImportTokensModal, } from '../../../../../store/actions'; import Tooltip from '../../../../ui/tooltip'; +import { useMultichainSelector } from '../../../../../hooks/useMultichainSelector'; +import { getMultichainNetwork } from '../../../../../selectors/multichain'; type AssetListControlBarProps = { showTokensLinks?: boolean; @@ -72,6 +75,9 @@ const AssetListControlBar = ({ showTokensLinks }: AssetListControlBarProps) => { const [isNetworkFilterPopoverOpen, setIsNetworkFilterPopoverOpen] = useState(false); + const account = useSelector(getSelectedInternalAccount); + const { isEvmNetwork } = useMultichainSelector(getMultichainNetwork, account); + const isTestNetwork = useMemo(() => { return (TEST_CHAINS as string[]).includes(currentNetwork.chainId); }, [currentNetwork.chainId, TEST_CHAINS]); @@ -166,12 +172,13 @@ const AssetListControlBar = ({ showTokensLinks }: AssetListControlBarProps) => { - {process.env.PORTFOLIO_VIEW && ( + {/* TODO: Remove isEvmNetwork check when we are ready to show the network filter in all networks including non-EVM */} + {process.env.PORTFOLIO_VIEW && isEvmNetwork ? ( { ? currentNetwork?.nickname ?? t('currentNetwork') : t('popularNetworks')} - )} + ) : null} ({ })); jest.mock('../../../selectors', () => ({ - getCurrentCurrency: jest.fn(), getSelectedAccount: jest.fn(), getPreferences: jest.fn(), getShouldHideZeroBalanceTokens: jest.fn(), @@ -40,6 +39,10 @@ jest.mock('../../../selectors', () => ({ getChainIdsToPoll: jest.fn(), })); +jest.mock('../../../ducks/metamask/metamask', () => ({ + getCurrentCurrency: jest.fn(), +})); + jest.mock('../../../../shared/modules/selectors/networks', () => ({ getNetworkConfigurationsByChainId: jest.fn(), })); diff --git a/ui/components/app/wallet-overview/aggregated-percentage-overview-cross-chains.tsx b/ui/components/app/wallet-overview/aggregated-percentage-overview-cross-chains.tsx index 6f7de0c95c7c..5a321c35cf0f 100644 --- a/ui/components/app/wallet-overview/aggregated-percentage-overview-cross-chains.tsx +++ b/ui/components/app/wallet-overview/aggregated-percentage-overview-cross-chains.tsx @@ -5,13 +5,13 @@ import { toChecksumAddress } from 'ethereumjs-util'; import { getNativeTokenAddress } from '@metamask/assets-controllers'; import { Hex } from '@metamask/utils'; import { - getCurrentCurrency, getSelectedAccount, getShouldHideZeroBalanceTokens, getPreferences, getMarketData, getChainIdsToPoll, } from '../../../selectors'; +import { getCurrentCurrency } from '../../../ducks/metamask/metamask'; // TODO: Remove restricted import // eslint-disable-next-line import/no-restricted-paths diff --git a/ui/components/app/wallet-overview/aggregated-percentage-overview.test.tsx b/ui/components/app/wallet-overview/aggregated-percentage-overview.test.tsx index 10fd24ec550c..cb6efa2516a9 100644 --- a/ui/components/app/wallet-overview/aggregated-percentage-overview.test.tsx +++ b/ui/components/app/wallet-overview/aggregated-percentage-overview.test.tsx @@ -2,8 +2,8 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; import { getIntlLocale } from '../../../ducks/locale/locale'; +import { getCurrentCurrency } from '../../../ducks/metamask/metamask'; import { - getCurrentCurrency, getSelectedAccount, getShouldHideZeroBalanceTokens, getTokensMarketData, @@ -21,8 +21,11 @@ jest.mock('../../../ducks/locale/locale', () => ({ getIntlLocale: jest.fn(), })); -jest.mock('../../../selectors', () => ({ +jest.mock('../../../ducks/metamask/metamask', () => ({ getCurrentCurrency: jest.fn(), +})); + +jest.mock('../../../selectors', () => ({ getSelectedAccount: jest.fn(), getPreferences: jest.fn(), getShouldHideZeroBalanceTokens: jest.fn(), diff --git a/ui/components/app/wallet-overview/aggregated-percentage-overview.tsx b/ui/components/app/wallet-overview/aggregated-percentage-overview.tsx index d50feebbbd74..ad837ccd322c 100644 --- a/ui/components/app/wallet-overview/aggregated-percentage-overview.tsx +++ b/ui/components/app/wallet-overview/aggregated-percentage-overview.tsx @@ -3,8 +3,8 @@ import { useSelector } from 'react-redux'; import { toChecksumAddress } from 'ethereumjs-util'; import { getNativeTokenAddress } from '@metamask/assets-controllers'; +import { getCurrentCurrency } from '../../../ducks/metamask/metamask'; import { - getCurrentCurrency, getSelectedAccount, getShouldHideZeroBalanceTokens, getTokensMarketData, diff --git a/ui/components/multichain/asset-picker-amount/asset-balance/asset-balance-text.test.tsx b/ui/components/multichain/asset-picker-amount/asset-balance/asset-balance-text.test.tsx index be9d58f23968..88db613fd837 100644 --- a/ui/components/multichain/asset-picker-amount/asset-balance/asset-balance-text.test.tsx +++ b/ui/components/multichain/asset-picker-amount/asset-balance/asset-balance-text.test.tsx @@ -1,10 +1,9 @@ import React from 'react'; -import { Provider } from 'react-redux'; -import { render } from '@testing-library/react'; import { AssetType } from '../../../../../shared/constants/transaction'; import mockSendState from '../../../../../test/data/mock-send-state.json'; import configureStore from '../../../../store/store'; import { TextColor } from '../../../../helpers/constants/design-system'; +import { renderWithProvider } from '../../../../../test/jest/rendering'; import { AssetBalanceText } from './asset-balance-text'; const store = configureStore({ @@ -41,17 +40,22 @@ jest.mock('../utils', () => ({ getIsFiatPrimary: () => mockGetIsFiatPrimary(), })); +const MOCK_TOKEN_WITH_BALANCES_ONE = { + tokensWithBalances: [ + { string: "doesn't matter", symbol: "doesn't matter", address: '0x01' }, + ], +}; +const MOCK_TOKEN_WITH_BALANCES_TWO = { + tokensWithBalances: [{ string: '100', address: '0x01' }], +}; + describe('AssetBalanceText', () => { beforeEach(() => { jest.clearAllMocks(); }); it('matches snapshot', () => { - mockUseTokenTracker.mockReturnValue({ - tokensWithBalances: [ - { string: "doesn't matter", symbol: "doesn't matter", address: '0x01' }, - ], - }); + mockUseTokenTracker.mockReturnValue(MOCK_TOKEN_WITH_BALANCES_ONE); mockUseCurrencyDisplay.mockReturnValue([ 'undefined', { value: 'fiat value', suffix: 'suffix', prefix: 'prefix-' }, @@ -64,21 +68,18 @@ describe('AssetBalanceText', () => { type: AssetType.native, balance: '1000000', }; - const { asFragment } = render( - - - , + const { asFragment } = renderWithProvider( + , + store, ); expect(asFragment()).toMatchSnapshot(); }); it('renders fiat primary correctly', () => { - mockUseTokenTracker.mockReturnValue({ - tokensWithBalances: [{ string: "doesn't matter", address: '0x01' }], - }); + mockUseTokenTracker.mockReturnValue(MOCK_TOKEN_WITH_BALANCES_ONE); mockUseCurrencyDisplay.mockReturnValue([ 'title', { value: '$1.23', symbol: "doesn't matter" }, @@ -92,20 +93,18 @@ describe('AssetBalanceText', () => { details: { address: '0x01', decimals: 2 }, balance: '100', }; - const { getByText } = render( - - {/* Replace `any` with type */} - {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} - - , + const { getByText } = renderWithProvider( + , + store, ); expect(getByText('$1.23')).toBeInTheDocument(); }); it('renders native asset type correctly', () => { - mockUseTokenTracker.mockReturnValue({ - tokensWithBalances: [{ string: '100', address: '0x01' }], - }); + mockUseTokenTracker.mockReturnValue(MOCK_TOKEN_WITH_BALANCES_TWO); mockUseCurrencyDisplay.mockReturnValue([ 'title', { value: 'test_balance' }, @@ -119,21 +118,86 @@ describe('AssetBalanceText', () => { balance: '10000', }; - const { getByText } = render( - - {/* Replace `any` with type */} - {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} - - , + const { getByText } = renderWithProvider( + , + store, ); expect(getByText('test_balance')).toBeInTheDocument(); }); - it('renders error correctly', () => { - mockUseTokenTracker.mockReturnValue({ - tokensWithBalances: [{ string: '100', address: '0x01' }], - }); + it('renders a single NFT correctly', () => { + mockUseTokenTracker.mockReturnValue(MOCK_TOKEN_WITH_BALANCES_TWO); + const asset = { + type: AssetType.NFT, + balance: '0x1', + }; + mockUseCurrencyDisplay.mockReturnValue([ + 'title', + { value: 'test_balance' }, + ]); + const { getByTestId } = renderWithProvider( + , + store, + ); + expect( + getByTestId('asset-balance-nft-display').textContent, + ).toMatchInlineSnapshot(`"1 NFT"`); + }); + + it('renders multiple NFTs correctly', () => { + mockUseTokenTracker.mockReturnValue(MOCK_TOKEN_WITH_BALANCES_TWO); + const asset = { + type: AssetType.NFT, + balance: '0x3', + }; + mockUseCurrencyDisplay.mockReturnValue([ + 'title', + { value: 'test_balance' }, + ]); + const { getByTestId } = renderWithProvider( + , + store, + ); + expect( + getByTestId('asset-balance-nft-display').textContent, + ).toMatchInlineSnapshot(`"3 NFTs"`); + }); + + it('renders NFT with error message', () => { + mockUseTokenTracker.mockReturnValue(MOCK_TOKEN_WITH_BALANCES_TWO); + const asset = { + type: AssetType.NFT, + balance: '0x3', + }; + mockUseCurrencyDisplay.mockReturnValue([ + 'title', + { value: 'test_balance' }, + ]); + const { getByTestId } = renderWithProvider( + , + store, + ); + expect( + getByTestId('asset-balance-nft-display').textContent, + ).toMatchInlineSnapshot(`"3 NFTs. Insufficient funds for gas"`); + }); + + it('renders error message correctly', () => { + mockUseTokenTracker.mockReturnValue(MOCK_TOKEN_WITH_BALANCES_TWO); mockUseCurrencyDisplay.mockReturnValue([ 'title', { value: 'test_balance' }, @@ -147,17 +211,18 @@ describe('AssetBalanceText', () => { balance: '10000', }; - const { getByText } = render( - - - , + const { getByText, getByTestId } = renderWithProvider( + , + store, ); expect(getByText('test_balance')).toBeInTheDocument(); - expect(getByText('. [errorText]')).toBeInTheDocument(); + expect( + getByTestId('send-page-amount-error').textContent, + ).toMatchInlineSnapshot(`". Insufficient funds for gas"`); }); }); diff --git a/ui/components/multichain/asset-picker-amount/asset-balance/asset-balance-text.tsx b/ui/components/multichain/asset-picker-amount/asset-balance/asset-balance-text.tsx index 67def7ee82b1..3aec458341bd 100644 --- a/ui/components/multichain/asset-picker-amount/asset-balance/asset-balance-text.tsx +++ b/ui/components/multichain/asset-picker-amount/asset-balance/asset-balance-text.tsx @@ -4,10 +4,8 @@ import { Text } from '../../../component-library'; import UserPreferencedCurrencyDisplay from '../../../app/user-preferenced-currency-display'; import { PRIMARY } from '../../../../helpers/constants/common'; import { Asset } from '../../../../ducks/send'; -import { - getCurrentCurrency, - getSelectedAccountCachedBalance, -} from '../../../../selectors'; +import { getSelectedAccountCachedBalance } from '../../../../selectors'; +import { getCurrentCurrency } from '../../../../ducks/metamask/metamask'; import { AssetType } from '../../../../../shared/constants/transaction'; import { TextColor, @@ -84,16 +82,18 @@ export function AssetBalanceText({ variant: TextVariant.bodySm, }, }; - const errorText = error ? `. ${t(error)}` : ''; if (asset.type === AssetType.NFT) { const numberOfTokens = hexToDecimal(asset.balance || '0x0'); return ( - - {`${numberOfTokens} ${t( - numberOfTokens === '1' ? 'token' : 'tokens', - )?.toLowerCase()}${errorText}`} + + {`${t( + numberOfTokens === '1' + ? 'assetSingleNFTBalance' + : 'assetMultipleNFTsBalance', + [numberOfTokens], + )}${errorText}`} ); } diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/Asset.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/Asset.tsx index 5561af182dc0..e247409f848e 100644 --- a/ui/components/multichain/asset-picker-amount/asset-picker-modal/Asset.tsx +++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/Asset.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { useSelector } from 'react-redux'; import { BigNumber } from 'bignumber.js'; +import { getCurrentCurrency } from '../../../../ducks/metamask/metamask'; import { - getCurrentCurrency, getNetworkConfigurationIdByChainId, getTokenList, } from '../../../../selectors'; diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/AssetList.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/AssetList.tsx index ece3d7046fde..740425114ab6 100644 --- a/ui/components/multichain/asset-picker-amount/asset-picker-modal/AssetList.tsx +++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/AssetList.tsx @@ -7,11 +7,13 @@ import { } from '@metamask/network-controller'; import { getCurrentChainId } from '../../../../../shared/modules/selectors/networks'; import { - getCurrentCurrency, getCurrentNetwork, getSelectedAccountCachedBalance, } from '../../../../selectors'; -import { getNativeCurrency } from '../../../../ducks/metamask/metamask'; +import { + getCurrentCurrency, + getNativeCurrency, +} from '../../../../ducks/metamask/metamask'; import { useCurrencyDisplay } from '../../../../hooks/useCurrencyDisplay'; import { AssetType } from '../../../../../shared/constants/transaction'; import { Box } from '../../../component-library'; diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal-network.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal-network.tsx index 7d6963240619..862e8e842b8d 100644 --- a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal-network.tsx +++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal-network.tsx @@ -32,7 +32,7 @@ import { useI18nContext } from '../../../../hooks/useI18nContext'; ///: END:ONLY_INCLUDE_IF import { NetworkListItem } from '../../network-list-item'; import { getNetworkConfigurationsByChainId } from '../../../../../shared/modules/selectors/networks'; -import { getCurrentCurrency } from '../../../../selectors'; +import { getCurrentCurrency } from '../../../../ducks/metamask/metamask'; import { formatCurrency } from '../../../../helpers/utils/confirm-tx.util'; import { useMultichainBalances } from '../../../../hooks/useMultichainBalances'; import { NETWORK_TO_SHORT_NETWORK_NAME_MAP } from '../../../../../shared/constants/bridge'; diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.test.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.test.tsx index 8fb933ed2fbb..dfff37c45ad1 100644 --- a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.test.tsx +++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.test.tsx @@ -12,7 +12,6 @@ import { renderWithProvider } from '../../../../../test/lib/render-helpers'; import mockState from '../../../../../test/data/mock-send-state.json'; import { AssetType } from '../../../../../shared/constants/transaction'; import { - getCurrentCurrency, getNativeCurrencyImage, getSelectedAccountCachedBalance, getSelectedInternalAccount, @@ -24,6 +23,7 @@ import { getConversionRate, getNativeCurrency, getTokens, + getCurrentCurrency, } from '../../../../ducks/metamask/metamask'; import { getTopAssets } from '../../../../ducks/swaps/swaps'; import { getRenderableTokenData } from '../../../../hooks/useTokensToSearch'; diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx index b8bf0a70d7f5..d3daf33afc84 100644 --- a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx +++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx @@ -36,7 +36,6 @@ import { } from '../../../../../shared/modules/selectors/networks'; import { getAllTokens, - getCurrentCurrency, getNativeCurrencyImage, getSelectedAccountCachedBalance, getSelectedInternalAccount, @@ -46,6 +45,7 @@ import { } from '../../../../selectors'; import { getConversionRate, + getCurrentCurrency, getNativeCurrency, } from '../../../../ducks/metamask/metamask'; import { useTokenTracker } from '../../../../hooks/useTokenTracker'; diff --git a/ui/components/multichain/pages/send/components/quote-card/hooks/useEthFeeData.test.tsx b/ui/components/multichain/pages/send/components/quote-card/hooks/useEthFeeData.test.tsx index 1ef3347e3133..9fa58e610bba 100644 --- a/ui/components/multichain/pages/send/components/quote-card/hooks/useEthFeeData.test.tsx +++ b/ui/components/multichain/pages/send/components/quote-card/hooks/useEthFeeData.test.tsx @@ -4,10 +4,10 @@ import { getNativeCurrency, getConversionRate, getGasFeeEstimates, + getCurrentCurrency, } from '../../../../../../../ducks/metamask/metamask'; import { getUsedSwapsGasPrice } from '../../../../../../../ducks/swaps/swaps'; import { - getCurrentCurrency, checkNetworkAndAccountSupports1559, getIsSwapsChain, } from '../../../../../../../selectors'; diff --git a/ui/components/multichain/pages/send/components/quote-card/hooks/useEthFeeData.tsx b/ui/components/multichain/pages/send/components/quote-card/hooks/useEthFeeData.tsx index 04fa59ce072a..f08ab47086a9 100644 --- a/ui/components/multichain/pages/send/components/quote-card/hooks/useEthFeeData.tsx +++ b/ui/components/multichain/pages/send/components/quote-card/hooks/useEthFeeData.tsx @@ -6,10 +6,10 @@ import { getConversionRate, getGasFeeEstimates, getNativeCurrency, + getCurrentCurrency, } from '../../../../../../../ducks/metamask/metamask'; import { EtherDenomination } from '../../../../../../../../shared/constants/common'; import { - getCurrentCurrency, checkNetworkAndAccountSupports1559, getIsSwapsChain, } from '../../../../../../../selectors/selectors'; diff --git a/ui/components/multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change.test.tsx b/ui/components/multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change.test.tsx index f157afa64d8b..53ff99bd5d06 100644 --- a/ui/components/multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change.test.tsx +++ b/ui/components/multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change.test.tsx @@ -5,13 +5,13 @@ import { zeroAddress } from 'ethereumjs-util'; import { MarketDataDetails } from '@metamask/assets-controllers'; import { getIntlLocale } from '../../../../../ducks/locale/locale'; import { - getCurrentCurrency, getSelectedAccountCachedBalance, getTokensMarketData, } from '../../../../../selectors'; import { getCurrentChainId } from '../../../../../../shared/modules/selectors/networks'; import { getConversionRate, + getCurrentCurrency, getNativeCurrency, } from '../../../../../ducks/metamask/metamask'; import { PercentageAndAmountChange } from './percentage-and-amount-change'; @@ -25,7 +25,6 @@ jest.mock('../../../../../ducks/locale/locale', () => ({ })); jest.mock('../../../../../selectors', () => ({ - getCurrentCurrency: jest.fn(), getSelectedAccountCachedBalance: jest.fn(), getTokensMarketData: jest.fn(), })); @@ -35,6 +34,7 @@ jest.mock('../../../../../../shared/modules/selectors/networks', () => ({ })); jest.mock('../../../../../ducks/metamask/metamask', () => ({ + getCurrentCurrency: jest.fn(), getConversionRate: jest.fn(), getNativeCurrency: jest.fn(), })); diff --git a/ui/components/multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change.tsx b/ui/components/multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change.tsx index dc0aeaa5a25c..c0c3e83addb7 100644 --- a/ui/components/multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change.tsx +++ b/ui/components/multichain/token-list-item/price/percentage-and-amount-change/percentage-and-amount-change.tsx @@ -11,7 +11,6 @@ import { } from '../../../../../helpers/constants/design-system'; import { getCurrentChainId } from '../../../../../../shared/modules/selectors/networks'; import { - getCurrentCurrency, getSelectedAccountCachedBalance, getTokensMarketData, } from '../../../../../selectors'; @@ -20,6 +19,7 @@ import { EtherDenomination } from '../../../../../../shared/constants/common'; import { Numeric } from '../../../../../../shared/modules/Numeric'; import { getConversionRate, + getCurrentCurrency, getNativeCurrency, } from '../../../../../ducks/metamask/metamask'; import { diff --git a/ui/components/ui/button-group/__snapshots__/button-group-component.test.js.snap b/ui/components/ui/button-group/__snapshots__/button-group-component.test.js.snap index 7e8116227458..b04aa2fbd4f1 100644 --- a/ui/components/ui/button-group/__snapshots__/button-group-component.test.js.snap +++ b/ui/components/ui/button-group/__snapshots__/button-group-component.test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ButtonGroup Component should match snapshot 1`] = ` +exports[`ButtonGroup Component should match snapshot with default variant 1`] = `
`; + +exports[`ButtonGroup Component should match snapshot with radiogroup variant 1`] = ` +
+
+ +
+
+`; diff --git a/ui/components/ui/button-group/button-group-component.test.js b/ui/components/ui/button-group/button-group-component.test.js index 2aaccad78a0c..584042491867 100644 --- a/ui/components/ui/button-group/button-group-component.test.js +++ b/ui/components/ui/button-group/button-group-component.test.js @@ -16,15 +16,25 @@ describe('ButtonGroup Component', () => { , - , - , +
diff --git a/ui/pages/bridge/prepare/__snapshots__/bridge-cta-button.test.tsx.snap b/ui/pages/bridge/prepare/__snapshots__/bridge-cta-button.test.tsx.snap index 0b46d2764523..6014dbaddb23 100644 --- a/ui/pages/bridge/prepare/__snapshots__/bridge-cta-button.test.tsx.snap +++ b/ui/pages/bridge/prepare/__snapshots__/bridge-cta-button.test.tsx.snap @@ -2,9 +2,13 @@ exports[`BridgeCTAButton should disable the component when quotes are loading and there are no existing quotes 1`] = `
-

+

+

+

`; @@ -23,20 +27,28 @@ exports[`BridgeCTAButton should enable the component when quotes are loading and exports[`BridgeCTAButton should render the component when amount and dest token is missing 1`] = `
-

- Select token and amount -

+

+ Select token and amount +

+
`; exports[`BridgeCTAButton should render the component's initial state 1`] = `
-

- Select token -

+

+ Select token +

+
`; diff --git a/ui/pages/bridge/prepare/__snapshots__/prepare-bridge-page.test.tsx.snap b/ui/pages/bridge/prepare/__snapshots__/prepare-bridge-page.test.tsx.snap index d5b980d13fd6..c69368d0b88e 100644 --- a/ui/pages/bridge/prepare/__snapshots__/prepare-bridge-page.test.tsx.snap +++ b/ui/pages/bridge/prepare/__snapshots__/prepare-bridge-page.test.tsx.snap @@ -172,11 +172,15 @@ exports[`PrepareBridgePage should render the component, with initial state 1`] = @@ -357,11 +361,15 @@ exports[`PrepareBridgePage should render the component, with inputs set 1`] = ` diff --git a/ui/pages/bridge/prepare/bridge-cta-button.test.tsx b/ui/pages/bridge/prepare/bridge-cta-button.test.tsx index d4e0fd0855c5..5dc368043d3a 100644 --- a/ui/pages/bridge/prepare/bridge-cta-button.test.tsx +++ b/ui/pages/bridge/prepare/bridge-cta-button.test.tsx @@ -23,7 +23,7 @@ describe('BridgeCTAButton', () => { bridgeSliceOverrides: { fromTokenInputValue: 1 }, }); const { container, getByText } = renderWithProvider( - , + , configureStore(mockStore), ); @@ -54,7 +54,7 @@ describe('BridgeCTAButton', () => { }, }); const { getByText } = renderWithProvider( - , + , configureStore(mockStore), ); @@ -83,7 +83,7 @@ describe('BridgeCTAButton', () => { }, }); const { getByText, container } = renderWithProvider( - , + , configureStore(mockStore), ); @@ -118,7 +118,7 @@ describe('BridgeCTAButton', () => { }, }); const { getByText, getByRole } = renderWithProvider( - , + , configureStore(mockStore), ); @@ -159,7 +159,7 @@ describe('BridgeCTAButton', () => { }, }); const { container } = renderWithProvider( - , + , configureStore(mockStore), ); @@ -199,7 +199,7 @@ describe('BridgeCTAButton', () => { }, }); const { getByText, getByRole, container } = renderWithProvider( - , + , configureStore(mockStore), ); diff --git a/ui/pages/bridge/prepare/bridge-cta-button.tsx b/ui/pages/bridge/prepare/bridge-cta-button.tsx index 8812e26eb099..8e45f90cd1db 100644 --- a/ui/pages/bridge/prepare/bridge-cta-button.tsx +++ b/ui/pages/bridge/prepare/bridge-cta-button.tsx @@ -1,6 +1,7 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { useSelector } from 'react-redux'; import { + ButtonLink, ButtonPrimary, ButtonPrimarySize, Text, @@ -18,7 +19,9 @@ import { import { useI18nContext } from '../../../hooks/useI18nContext'; import useSubmitBridgeTransaction from '../hooks/useSubmitBridgeTransaction'; import { + AlignItems, BlockSize, + JustifyContent, TextAlign, TextColor, TextVariant, @@ -32,8 +35,14 @@ import { useTradeProperties } from '../../../hooks/bridge/events/useTradePropert import { MetaMetricsEventName } from '../../../../shared/constants/metametrics'; import { SWAPS_CHAINID_DEFAULT_TOKEN_MAP } from '../../../../shared/constants/swaps'; import { getNativeCurrency } from '../../../ducks/metamask/metamask'; - -export const BridgeCTAButton = () => { +import { Row } from '../layout'; +import { isQuoteExpired as isQuoteExpiredUtil } from '../utils/quote'; + +export const BridgeCTAButton = ({ + onFetchNewQuotes, +}: { + onFetchNewQuotes: () => void; +}) => { const t = useI18nContext(); const fromToken = useSelector(getFromToken); @@ -43,9 +52,14 @@ export const BridgeCTAButton = () => { const fromAmount = useSelector(getFromAmount); - const { isLoading, activeQuote, isQuoteGoingToRefresh, quotesRefreshCount } = + const { isLoading, activeQuote, isQuoteGoingToRefresh, quotesLastFetchedMs } = useSelector(getBridgeQuotes); - const { maxRefreshCount, refreshRate } = useSelector(getBridgeQuotesConfig); + const { refreshRate } = useSelector(getBridgeQuotesConfig); + const isQuoteExpired = isQuoteExpiredUtil( + isQuoteGoingToRefresh, + refreshRate, + quotesLastFetchedMs, + ); const { submitBridgeTransaction } = useSubmitBridgeTransaction(); const [isSubmitting, setIsSubmitting] = useState(false); @@ -76,7 +90,6 @@ export const BridgeCTAButton = () => { const tradeProperties = useTradeProperties(); const ticker = useSelector(getNativeCurrency); - const [isQuoteExpired, setIsQuoteExpired] = useState(false); const isInsufficientBalance = isInsufficientBalance_(balanceAmount); @@ -85,28 +98,12 @@ export const BridgeCTAButton = () => { const isInsufficientGasForQuote = isInsufficientGasForQuote_(nativeAssetBalance); - useEffect(() => { - let timeout: NodeJS.Timeout; - // Reset the isQuoteExpired if quote fethching restarts - if (quotesRefreshCount === 0) { - setIsQuoteExpired(false); - return () => clearTimeout(timeout); - } - // After the last quote refresh, set a timeout to expire the quote and disable the CTA - if (quotesRefreshCount >= maxRefreshCount && !isQuoteGoingToRefresh) { - timeout = setTimeout(() => { - setIsQuoteExpired(true); - }, refreshRate); - } - return () => clearTimeout(timeout); - }, [isQuoteGoingToRefresh, quotesRefreshCount]); - const label = useMemo(() => { if (wasTxDeclined) { return t('youDeclinedTheTransaction'); } - if (isQuoteExpired && !isNoQuotesAvailable) { + if (isQuoteExpired) { return t('bridgeQuoteExpired'); } @@ -149,7 +146,15 @@ export const BridgeCTAButton = () => { isQuoteExpired, ]); - return activeQuote && !wasTxDeclined ? ( + // Label for the secondary button that re-starts quote fetching + const secondaryButtonLabel = useMemo(() => { + if (wasTxDeclined || isQuoteExpired) { + return t('bridgeFetchNewQuotes'); + } + return undefined; + }, [wasTxDeclined, isQuoteExpired]); + + return activeQuote && !secondaryButtonLabel ? ( { {label} ) : ( - - {label} - + + {label} + + {secondaryButtonLabel && ( + + {secondaryButtonLabel} + + )} + ); }; diff --git a/ui/pages/bridge/prepare/bridge-input-group.tsx b/ui/pages/bridge/prepare/bridge-input-group.tsx index c4502725c1ea..1734624b7dcc 100644 --- a/ui/pages/bridge/prepare/bridge-input-group.tsx +++ b/ui/pages/bridge/prepare/bridge-input-group.tsx @@ -14,7 +14,8 @@ import { import { AssetPicker } from '../../../components/multichain/asset-picker-amount/asset-picker'; import { TabName } from '../../../components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal-tabs'; import { useI18nContext } from '../../../hooks/useI18nContext'; -import { getCurrentCurrency, getLocale } from '../../../selectors'; +import { getLocale } from '../../../selectors'; +import { getCurrentCurrency } from '../../../ducks/metamask/metamask'; import { formatCurrencyAmount, formatTokenAmount } from '../utils/quote'; import { Column, Row, Tooltip } from '../layout'; import { diff --git a/ui/pages/bridge/prepare/prepare-bridge-page.tsx b/ui/pages/bridge/prepare/prepare-bridge-page.tsx index b4774ec5489a..fc076b73629b 100644 --- a/ui/pages/bridge/prepare/prepare-bridge-page.tsx +++ b/ui/pages/bridge/prepare/prepare-bridge-page.tsx @@ -67,7 +67,11 @@ import { hexToDecimal } from '../../../../shared/modules/conversion.utils'; import { QuoteRequest } from '../types'; import { calcTokenValue } from '../../../../shared/lib/swaps-utils'; import { BridgeQuoteCard } from '../quotes/bridge-quote-card'; -import { formatTokenAmount, isValidQuoteRequest } from '../utils/quote'; +import { + formatTokenAmount, + isQuoteExpired as isQuoteExpiredUtil, + isValidQuoteRequest, +} from '../utils/quote'; import { getProviderConfig } from '../../../../shared/modules/selectors/networks'; import { CrossChainSwapsEventProperties, @@ -121,12 +125,24 @@ const PrepareBridgePage = () => { const slippage = useSelector(getSlippage); const quoteRequest = useSelector(getQuoteRequest); - const { isLoading, activeQuote, isQuoteGoingToRefresh } = - useSelector(getBridgeQuotes); - + const { + isLoading, + activeQuote: activeQuote_, + isQuoteGoingToRefresh, + quotesLastFetchedMs, + } = useSelector(getBridgeQuotes); const { refreshRate } = useSelector(getBridgeQuotesConfig); const wasTxDeclined = useSelector(getWasTxDeclined); + // If latest quote is expired and user has sufficient balance + // set activeQuote to undefined to hide stale quotes but keep inputs filled + const isQuoteExpired = isQuoteExpiredUtil( + isQuoteGoingToRefresh, + refreshRate, + quotesLastFetchedMs, + ); + const activeQuote = + isQuoteExpired && !quoteRequest.insufficientBal ? undefined : activeQuote_; const keyring = useSelector(getCurrentKeyring); // @ts-expect-error keyring type is wrong maybe? @@ -533,9 +549,13 @@ const PrepareBridgePage = () => { backgroundColor={BackgroundColor.primaryMuted} /> )} - {!wasTxDeclined && } + {!wasTxDeclined && activeQuote && }