From d2098e26acc5306094be87d4bc6d5a75d66d4c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CGoktug?= <“goktug.poyraz@consensys.net”> Date: Fri, 10 Mar 2023 13:22:51 +0100 Subject: [PATCH 1/9] fix: fix theme issues of desktop pairing page --- shared/lib/error-utils.js | 4 +- .../desktop-pairing.test.js.snap | 246 +++++++++--------- .../desktop-pairing.component.js | 132 +++++----- ui/pages/desktop-pairing/index.scss | 107 +------- 4 files changed, 204 insertions(+), 285 deletions(-) diff --git a/shared/lib/error-utils.js b/shared/lib/error-utils.js index d5f5feb3fcd0..b04a2f72a38e 100644 --- a/shared/lib/error-utils.js +++ b/shared/lib/error-utils.js @@ -126,7 +126,9 @@ function disableDesktop(backgroundConnection) { } export function downloadDesktopApp() { - global.platform.openTab({ url: 'https://metamask.io/' }); + global.platform.openTab({ + url: 'https://github.com/MetaMask/metamask-desktop/releases', + }); } export function downloadExtension() { diff --git a/ui/pages/desktop-pairing/__snapshots__/desktop-pairing.test.js.snap b/ui/pages/desktop-pairing/__snapshots__/desktop-pairing.test.js.snap index dcead5fb5d1b..c3a42d0353ed 100644 --- a/ui/pages/desktop-pairing/__snapshots__/desktop-pairing.test.js.snap +++ b/ui/pages/desktop-pairing/__snapshots__/desktop-pairing.test.js.snap @@ -3,139 +3,134 @@ exports[`Desktop Pairing page should render otp component 1`] = `
-
-
- - - - - - - - - - - - - - - - - - - - -
-
-
- Pair with Desktop -
-
- Open your MetaMask Desktop and type this code -
+ + + + + + + + + + + + + + + + + + +
+

+ Pair with Desktop +

+

+ Open your MetaMask Desktop and type this code +

-
-
-

- 123456 -

-
-
+ 123456 +

+
+

@@ -149,15 +144,16 @@ exports[`Desktop Pairing page should render otp component 1`] = `

-
- If the pairing is successful, extension will restart and you'll have to re-enter your password. -
+

+ If the pairing is successful, extension will restart and you'll have to re-enter your password. +

-
+ ); }; return ( -
-
- {renderIcon()} -
{t('desktopPageTitle')}
-
- {t('desktopPageSubTitle')} -
-
-
{renderContent()}
+ + {renderIcon()} + {renderContent()} {renderFooter()} -
+ ); } diff --git a/ui/pages/desktop-pairing/index.scss b/ui/pages/desktop-pairing/index.scss index cf634e8fb28e..46603a2596f7 100644 --- a/ui/pages/desktop-pairing/index.scss +++ b/ui/pages/desktop-pairing/index.scss @@ -3,126 +3,39 @@ flex-flow: column; align-items: center; padding: 0 30px 0; + max-width: 640px; &__countdown-timer { - background: #f2f3f4; - border-radius: 15.5px; - padding: 7px 0 7px 0; - margin: 0 32px 0 32px; + background: var(--color-background-default); + border-radius: 16px; + padding: 8px 0; font-size: 12px; + width: 240px; } &__countdown-timer-seconds { color: var(--color-primary-default); } - &__icon { - padding-top: 24px; - } - - &__title { - font-style: normal; - font-weight: 700; - font-size: 24px; - line-height: 140.62%; - text-align: center; - color: #24292e; - padding-top: 24px; - } - - &__subtitle, - &__description { - font-style: normal; - font-weight: 400; - font-size: 14px; - line-height: 140.62%; - text-align: center; - color: #000; - padding-top: 8px; - margin: 0 56px; - } - - &__description { - margin: 18px 0; - } - &__otp { font-style: normal; font-weight: 500; font-size: 48px; + line-height: 48px; letter-spacing: 10px; - color: #000; - } - - &__buttons { - display: flex; - width: 70%; - justify-content: space-between; - margin: auto; } &__tooltip-wrapper { width: 100%; } - &__clickable { - width: 100%; - - &:hover { - cursor: pointer; - } - } -} - -.desktop-pairing-warning { - font-style: normal; - font-weight: 400; - font-size: 14px; - - &__close-button { - z-index: 1050; - font-size: 24px; - cursor: pointer; - - &__close::after { - content: '\00D7'; - font-size: 24px; - cursor: pointer; - position: relative; - float: right; - margin-top: -8px; - } - } - - &__title { - font-weight: bold; - font-size: 16px; - } - - &__text { - font-size: 0.875rem; - } - - &__link { - color: var(--color-primary-default); - display: block; + &__clickable:hover { cursor: pointer; - line-height: 100%; - font-size: 0.875rem; - padding: 0 0; } - &__warning-content { - border-left: 5px solid var(--color-warning-default); - border-right: 0; - border-bottom: 0; - border-top: 0; - margin: 4px; - - .icon { - position: absolute; - left: 5px; - top: 10px; + &__icon { + path { + fill: var(--color-text-default); } } } From d2b44a338d22fb1728264ce4243537c95f7c5ad8 Mon Sep 17 00:00:00 2001 From: Goktug Poyraz Date: Mon, 20 Mar 2023 14:17:40 +0100 Subject: [PATCH 2/9] fix: download link --- shared/lib/error-utils.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/shared/lib/error-utils.js b/shared/lib/error-utils.js index b04a2f72a38e..b473b57b4ae3 100644 --- a/shared/lib/error-utils.js +++ b/shared/lib/error-utils.js @@ -121,13 +121,16 @@ export async function getErrorHtml( } ///: BEGIN:ONLY_INCLUDE_IN(flask) +const MMD_DOWNLOAD_LINK = + 'https://github.com/MetaMask/metamask-desktop/releases'; + function disableDesktop(backgroundConnection) { backgroundConnection.disableDesktopError(); } export function downloadDesktopApp() { global.platform.openTab({ - url: 'https://github.com/MetaMask/metamask-desktop/releases', + url: MMD_DOWNLOAD_LINK, }); } @@ -141,7 +144,7 @@ export function restartExtension() { export function openOrDownloadMMD() { openCustomProtocol('metamask-desktop://pair').catch(() => { - window.open('https://metamask.io/download.html', '_blank').focus(); + window.open(MMD_DOWNLOAD_LINK, '_blank').focus(); }); } From c1b607bdba4be224d7c1c57ffd9565a37b4cb6d4 Mon Sep 17 00:00:00 2001 From: Goktug Poyraz Date: Mon, 20 Mar 2023 14:28:46 +0100 Subject: [PATCH 3/9] Fix: add tests --- shared/lib/error-utils.js | 2 +- shared/lib/error-utils.test.js | 126 +++++++++++++++++- .../desktop-pairing/desktop-pairing.test.js | 56 +++++++- 3 files changed, 181 insertions(+), 3 deletions(-) diff --git a/shared/lib/error-utils.js b/shared/lib/error-utils.js index b473b57b4ae3..631fd04b063d 100644 --- a/shared/lib/error-utils.js +++ b/shared/lib/error-utils.js @@ -121,7 +121,7 @@ export async function getErrorHtml( } ///: BEGIN:ONLY_INCLUDE_IN(flask) -const MMD_DOWNLOAD_LINK = +export const MMD_DOWNLOAD_LINK = 'https://github.com/MetaMask/metamask-desktop/releases'; function disableDesktop(backgroundConnection) { diff --git a/shared/lib/error-utils.test.js b/shared/lib/error-utils.test.js index de846aa8bc41..f761acd1080b 100644 --- a/shared/lib/error-utils.test.js +++ b/shared/lib/error-utils.test.js @@ -1,13 +1,48 @@ +import browser from 'webextension-polyfill'; import { fetchLocale } from '../../ui/helpers/utils/i18n-helper'; import { SUPPORT_LINK } from './ui-utils'; -import { getErrorHtml } from './error-utils'; +import { + downloadDesktopApp, + openOrDownloadMMD, + downloadExtension, + getErrorHtml, + restartExtension, + registerDesktopErrorActions, + MMD_DOWNLOAD_LINK, +} from './error-utils'; +import { openCustomProtocol } from './deep-linking'; jest.mock('../../ui/helpers/utils/i18n-helper', () => ({ fetchLocale: jest.fn(), loadRelativeTimeFormatLocaleData: jest.fn(), })); +jest.mock('./deep-linking', () => ({ + openCustomProtocol: jest.fn(), +})); + +jest.mock('webextension-polyfill', () => { + return { + runtime: { + reload: jest.fn(), + }, + }; +}); describe('Error utils Tests', function () { + beforeEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + + global.platform = { + openTab: jest.fn(), + }; + }); + + afterAll(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + delete global.platform; + }); it('should get error html', async function () { const mockStore = { localeMessages: { @@ -50,4 +85,93 @@ describe('Error utils Tests', function () { expect(errorHtml).toContain(stillGettingMessageMessage); expect(errorHtml).toContain(sendBugReportMessage); }); + describe('desktop', () => { + it('downloadDesktopApp opens a new tab on metamask-desktop releases url', () => { + downloadDesktopApp(); + + expect(global.platform.openTab).toHaveBeenCalledTimes(1); + expect(global.platform.openTab).toHaveBeenCalledWith({ + url: MMD_DOWNLOAD_LINK, + }); + }); + + it('downloadExtension opens a new tab on metamask extension url', () => { + downloadExtension(); + + expect(global.platform.openTab).toHaveBeenCalledTimes(1); + expect(global.platform.openTab).toHaveBeenCalledWith({ + url: 'https://metamask.io/', + }); + }); + + it('restartExtension calls runtime reload method', () => { + restartExtension(); + + expect(browser.runtime.reload).toHaveBeenCalledTimes(1); + }); + + describe('openOrDownloadMMD', () => { + it('launches installed desktop app by calling openCustomProtocol successfully', () => { + openCustomProtocol.mockResolvedValue(); + openOrDownloadMMD(); + + expect(openCustomProtocol).toHaveBeenCalledTimes(1); + expect(openCustomProtocol).toHaveBeenCalledWith( + 'metamask-desktop://pair', + ); + }); + + it('opens metamask-desktop release url when fails to find and start a local metamask-desktop app', async () => { + openCustomProtocol.mockRejectedValue(); + const focusMock = jest.fn(); + jest.spyOn(window, 'open').mockReturnValue({ + focus: focusMock, + }); + + openOrDownloadMMD(); + + // this ensures that we are awaiting for pending promises to resolve + // as the openOrDownloadMMD calls a promise, but returns before it is resolved + await new Promise(process.nextTick); + + expect(openCustomProtocol).toHaveBeenCalledTimes(1); + expect(openCustomProtocol).toHaveBeenCalledWith( + 'metamask-desktop://pair', + ); + + expect(window.open).toHaveBeenCalledTimes(1); + expect(window.open).toHaveBeenCalledWith(MMD_DOWNLOAD_LINK, '_blank'); + expect(focusMock).toHaveBeenCalledTimes(1); + }); + }); + + it('registerDesktopErrorActions add click event listeners for each desktop error elements', async () => { + const addEventListenerMock = jest.fn(); + jest.spyOn(document, 'getElementById').mockReturnValue({ + addEventListener: addEventListenerMock, + }); + + registerDesktopErrorActions(); + + expect(document.getElementById).toHaveBeenCalledTimes(4); + expect(document.getElementById).toHaveBeenNthCalledWith( + 1, + 'desktop-error-button-disable-mmd', + ); + expect(document.getElementById).toHaveBeenNthCalledWith( + 2, + 'desktop-error-button-restart-mm', + ); + expect(document.getElementById).toHaveBeenNthCalledWith( + 3, + 'desktop-error-button-download-mmd', + ); + expect(document.getElementById).toHaveBeenNthCalledWith( + 4, + 'desktop-error-button-open-or-download-mmd', + ); + + expect(addEventListenerMock).toHaveBeenCalledTimes(4); + }); + }); }); diff --git a/ui/pages/desktop-pairing/desktop-pairing.test.js b/ui/pages/desktop-pairing/desktop-pairing.test.js index 371b13e64589..12a7be2b181a 100644 --- a/ui/pages/desktop-pairing/desktop-pairing.test.js +++ b/ui/pages/desktop-pairing/desktop-pairing.test.js @@ -1,11 +1,12 @@ import React from 'react'; import reactRouterDom from 'react-router-dom'; -import { waitFor, act, screen } from '@testing-library/react'; +import { waitFor, act, screen, fireEvent } from '@testing-library/react'; import actions from '../../store/actions'; import configureStore from '../../store/store'; import { renderWithProvider } from '../../../test/jest'; import mockState from '../../../test/data/mock-state.json'; import { SECOND } from '../../../shared/constants/time'; +import { useCopyToClipboard } from '../../hooks/useCopyToClipboard'; import DesktopPairingPage from '.'; const mockHideLoadingIndication = jest.fn(); @@ -19,10 +20,13 @@ jest.mock('../../store/actions', () => { }; }); +jest.mock('../../hooks/useCopyToClipboard'); + const mockedActions = actions; describe('Desktop Pairing page', () => { const mockHistoryPush = jest.fn(); + const handleCopy = jest.fn(); function flushPromises() { // Wait for promises running in the non-async timer callback to complete. @@ -35,6 +39,7 @@ describe('Desktop Pairing page', () => { .spyOn(reactRouterDom, 'useHistory') .mockImplementation() .mockReturnValue({ push: mockHistoryPush }); + useCopyToClipboard.mockReturnValue([false, handleCopy]); }); afterEach(() => { @@ -107,4 +112,53 @@ describe('Desktop Pairing page', () => { jest.clearAllTimers(); jest.useRealTimers(); }); + + it('should copy otp value when content is clicked', async () => { + const otp = '123456'; + mockedActions.generateDesktopOtp.mockResolvedValue(otp); + + const store = configureStore(mockState); + + act(() => { + renderWithProvider(, store); + }); + + await waitFor(() => { + expect(screen.getByTestId('desktop-pairing-otp-content')).toBeDefined(); + expect(screen.getByText(otp)).toBeDefined(); + }); + + act(() => { + fireEvent.click(screen.getByTestId('desktop-pairing-otp-content')); + }); + + await waitFor(() => { + expect(handleCopy).toHaveBeenCalledWith(otp); + }); + }); + + it('should return to previews page when the done button is clicked', async () => { + const otp = '123456'; + const mostRecentOverviewPage = '/mostRecentOverviewPage'; + mockedActions.generateDesktopOtp.mockResolvedValue(otp); + + const store = configureStore(mockState); + + act(() => { + renderWithProvider(, store); + }); + + await waitFor(() => { + expect(screen.getByTestId('desktop-pairing-otp-content')).toBeDefined(); + }); + + act(() => { + fireEvent.click(screen.getByText('Done')); + }); + + await waitFor(() => { + expect(mockHistoryPush).toHaveBeenCalledTimes(1); + expect(mockHistoryPush).toHaveBeenCalledWith(mostRecentOverviewPage); + }); + }); }); From 3a1bcf14466b40cec1d7a2a155a0479a891880e1 Mon Sep 17 00:00:00 2001 From: Goktug Poyraz Date: Tue, 21 Mar 2023 13:09:13 +0100 Subject: [PATCH 4/9] Adds desktop pairing page story and fix suggestions --- .../desktop-pairing.test.js.snap | 14 ++++----- .../desktop-pairing.component.js | 27 +++++++++-------- .../desktop-pairing.stories.js | 30 +++++++++++++++++++ ui/pages/desktop-pairing/index.scss | 17 ++--------- 4 files changed, 52 insertions(+), 36 deletions(-) create mode 100644 ui/pages/desktop-pairing/desktop-pairing.stories.js diff --git a/ui/pages/desktop-pairing/__snapshots__/desktop-pairing.test.js.snap b/ui/pages/desktop-pairing/__snapshots__/desktop-pairing.test.js.snap index c3a42d0353ed..586e8144478d 100644 --- a/ui/pages/desktop-pairing/__snapshots__/desktop-pairing.test.js.snap +++ b/ui/pages/desktop-pairing/__snapshots__/desktop-pairing.test.js.snap @@ -117,12 +117,12 @@ exports[`Desktop Pairing page should render otp component 1`] = ` style="display: inline;" tabindex="0" > -

123456 -

+

@@ -153,12 +153,10 @@ exports[`Desktop Pairing page should render otp component 1`] = `

diff --git a/ui/pages/desktop-pairing/desktop-pairing.component.js b/ui/pages/desktop-pairing/desktop-pairing.component.js index 098976b66a71..c529aa2619b1 100644 --- a/ui/pages/desktop-pairing/desktop-pairing.component.js +++ b/ui/pages/desktop-pairing/desktop-pairing.component.js @@ -1,7 +1,6 @@ import React, { useState, useEffect, useRef, useContext } from 'react'; import { useHistory } from 'react-router-dom'; import PropTypes from 'prop-types'; -import Button from '../../components/ui/button'; import { SECOND } from '../../../shared/constants/time'; import { I18nContext } from '../../contexts/i18n'; import IconDesktopPairing from '../../components/ui/icon/icon-desktop-pairing'; @@ -10,13 +9,14 @@ import { TextVariant, DISPLAY, AlignItems, - BLOCK_SIZES, JustifyContent, + BackgroundColor, + BorderRadius, } from '../../helpers/constants/design-system'; import Box from '../../components/ui/box/box'; import { useCopyToClipboard } from '../../hooks/useCopyToClipboard'; import Tooltip from '../../components/ui/tooltip'; -import { Text } from '../../components/component-library'; +import { Text, Button } from '../../components/component-library'; export default function DesktopPairingPage({ generateDesktopOtp, @@ -104,11 +104,7 @@ export default function DesktopPairingPage({ {t('desktopPageTitle')} - + {t('desktopPageSubTitle')} - + {otp} @@ -137,9 +137,12 @@ export default function DesktopPairingPage({ marginBottom={6} > {t('desktopPairingExpireMessage', [ { return ( - +

Pair with Desktop

Open your MetaMask Desktop and type this code

@@ -119,7 +119,7 @@ exports[`Desktop Pairing page should render otp component 1`] = ` >

123456

@@ -130,7 +130,7 @@ exports[`Desktop Pairing page should render otp component 1`] = ` >

@@ -147,7 +147,7 @@ exports[`Desktop Pairing page should render otp component 1`] = `

If the pairing is successful, extension will restart and you'll have to re-enter your password.

@@ -156,7 +156,7 @@ exports[`Desktop Pairing page should render otp component 1`] = ` class="box box--flex-direction-row" > From 8cba64b993feb1e6c4fece1fa13bac7267438cde Mon Sep 17 00:00:00 2001 From: David Walsh Date: Tue, 28 Mar 2023 09:50:02 -0500 Subject: [PATCH 6/9] Remove isEIP1559Account usage (#18064) --- ui/selectors/selectors.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 42510747566a..21db5560fc4d 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -177,10 +177,6 @@ export function getCurrentKeyring(state) { return keyring; } -export function isEIP1559Account() { - return true; -} - /** * The function returns true if network and account details are fetched and * both of them support EIP-1559. @@ -189,9 +185,7 @@ export function isEIP1559Account() { */ export function checkNetworkAndAccountSupports1559(state) { const networkSupports1559 = isEIP1559Network(state); - const accountSupports1559 = isEIP1559Account(state); - - return networkSupports1559 && accountSupports1559; + return networkSupports1559; } /** @@ -202,9 +196,7 @@ export function checkNetworkAndAccountSupports1559(state) { */ export function checkNetworkOrAccountNotSupports1559(state) { const networkNotSupports1559 = isNotEIP1559Network(state); - const accountSupports1559 = isEIP1559Account(state); - - return networkNotSupports1559 || accountSupports1559 === false; + return networkNotSupports1559; } /** From 870415f11152ff6cf3b61affff33032bab3d3bbf Mon Sep 17 00:00:00 2001 From: David Walsh Date: Tue, 28 Mar 2023 14:59:18 -0500 Subject: [PATCH 7/9] UX: Multichain: Global Action Menu (#18158) --- app/_locales/en/messages.json | 6 + ui/components/app/menu-bar/menu-bar.js | 19 ++- .../multichain/global-menu/global-menu.js | 155 ++++++++++++++++++ .../global-menu/global-menu.stories.js | 22 +++ .../global-menu/global-menu.test.js | 69 ++++++++ ui/components/multichain/global-menu/index.js | 1 + ui/components/multichain/index.js | 1 + 7 files changed, 267 insertions(+), 6 deletions(-) create mode 100644 ui/components/multichain/global-menu/global-menu.js create mode 100644 ui/components/multichain/global-menu/global-menu.stories.js create mode 100644 ui/components/multichain/global-menu/global-menu.test.js create mode 100644 ui/components/multichain/global-menu/index.js diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 5770bc805974..cf2ebbab5aee 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1967,6 +1967,9 @@ "lock": { "message": "Lock" }, + "lockMetaMask": { + "message": "Lock MetaMask" + }, "lockTimeTooGreat": { "message": "Lock time is too great" }, @@ -2949,6 +2952,9 @@ "portfolio": { "message": "Portfolio" }, + "portfolioView": { + "message": "Portfolio view" + }, "preferredLedgerConnectionType": { "message": "Preferred Ledger connection type", "description": "A header for a dropdown in Settings > Advanced. Appears above the ledgerConnectionPreferenceDescription message" diff --git a/ui/components/app/menu-bar/menu-bar.js b/ui/components/app/menu-bar/menu-bar.js index 548d3e7e1f2f..bf80cad5da6e 100644 --- a/ui/components/app/menu-bar/menu-bar.js +++ b/ui/components/app/menu-bar/menu-bar.js @@ -12,6 +12,7 @@ import { useI18nContext } from '../../../hooks/useI18nContext'; import { getOriginOfCurrentTab } from '../../../selectors'; import { MetaMetricsContext } from '../../../contexts/metametrics'; import { ButtonIcon, ICON_NAMES } from '../../component-library'; +import { GlobalMenu } from '../../multichain/global-menu'; import AccountOptionsMenu from './account-options-menu'; export default function MenuBar() { @@ -53,12 +54,18 @@ export default function MenuBar() { }} /> - {accountOptionsMenuOpen ? ( - setAccountOptionsMenuOpen(false)} - /> - ) : null} + {accountOptionsMenuOpen && + (process.env.MULTICHAIN ? ( + setAccountOptionsMenuOpen(false)} + /> + ) : ( + setAccountOptionsMenuOpen(false)} + /> + ))} ); } diff --git a/ui/components/multichain/global-menu/global-menu.js b/ui/components/multichain/global-menu/global-menu.js new file mode 100644 index 000000000000..4c2f738b9eb6 --- /dev/null +++ b/ui/components/multichain/global-menu/global-menu.js @@ -0,0 +1,155 @@ +import React, { useContext } from 'react'; +import PropTypes from 'prop-types'; +import { useHistory } from 'react-router-dom'; +import { useDispatch } from 'react-redux'; +import { + CONNECTED_ROUTE, + SETTINGS_ROUTE, + DEFAULT_ROUTE, +} from '../../../helpers/constants/routes'; +import { lockMetamask } from '../../../store/actions'; +import { useI18nContext } from '../../../hooks/useI18nContext'; +import { ICON_NAMES } from '../../component-library'; +import { Menu, MenuItem } from '../../ui/menu'; +import { getEnvironmentType } from '../../../../app/scripts/lib/util'; +import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../shared/constants/app'; +import { SUPPORT_LINK } from '../../../../shared/lib/ui-utils'; + +import { MetaMetricsContext } from '../../../contexts/metametrics'; +import { + EVENT_NAMES, + EVENT, + CONTEXT_PROPS, +} from '../../../../shared/constants/metametrics'; + +export const GlobalMenu = ({ closeMenu, anchorElement }) => { + const t = useI18nContext(); + const dispatch = useDispatch(); + const trackEvent = useContext(MetaMetricsContext); + const history = useHistory(); + + return ( + + { + history.push(CONNECTED_ROUTE); + trackEvent({ + event: EVENT_NAMES.NAV_CONNECTED_SITES_OPENED, + category: EVENT.CATEGORIES.NAVIGATION, + properties: { + location: 'Account Options', + }, + }); + closeMenu(); + }} + > + {t('connectedSites')} + + { + const portfolioUrl = process.env.PORTFOLIO_URL; + global.platform.openTab({ + url: `${portfolioUrl}?metamaskEntry=ext`, + }); + trackEvent( + { + category: EVENT.CATEGORIES.HOME, + event: EVENT_NAMES.PORTFOLIO_LINK_CLICKED, + properties: { + url: portfolioUrl, + }, + }, + { + contextPropsIntoEventProperties: [CONTEXT_PROPS.PAGE_TITLE], + }, + ); + closeMenu(); + }} + data-testid="global-menu-portfolio" + > + {t('portfolioView')} + + {getEnvironmentType() === ENVIRONMENT_TYPE_FULLSCREEN ? null : ( + { + global.platform.openExtensionInBrowser(); + trackEvent({ + event: EVENT_NAMES.APP_WINDOW_EXPANDED, + category: EVENT.CATEGORIES.NAVIGATION, + properties: { + location: 'Account Options', + }, + }); + closeMenu(); + }} + data-testid="global-menu-expand" + > + {t('expandView')} + + )} + { + global.platform.openTab({ url: SUPPORT_LINK }); + trackEvent( + { + category: EVENT.CATEGORIES.HOME, + event: EVENT_NAMES.SUPPORT_LINK_CLICKED, + properties: { + url: SUPPORT_LINK, + }, + }, + { + contextPropsIntoEventProperties: [CONTEXT_PROPS.PAGE_TITLE], + }, + ); + closeMenu(); + }} + data-testid="global-menu-support" + > + {t('support')} + + { + history.push(SETTINGS_ROUTE); + trackEvent({ + category: EVENT.CATEGORIES.NAVIGATION, + event: EVENT_NAMES.NAV_SETTINGS_OPENED, + properties: { + location: 'Main Menu', + }, + }); + closeMenu(); + }} + > + {t('settings')} + + { + dispatch(lockMetamask()); + history.push(DEFAULT_ROUTE); + closeMenu(); + }} + data-testid="global-menu-lock" + > + {t('lockMetaMask')} + + + ); +}; + +GlobalMenu.propTypes = { + /** + * The element that the menu should display next to + */ + anchorElement: PropTypes.instanceOf(window.Element), + /** + * Function that closes this menu + */ + closeMenu: PropTypes.func.isRequired, +}; diff --git a/ui/components/multichain/global-menu/global-menu.stories.js b/ui/components/multichain/global-menu/global-menu.stories.js new file mode 100644 index 000000000000..6206b21d1c4e --- /dev/null +++ b/ui/components/multichain/global-menu/global-menu.stories.js @@ -0,0 +1,22 @@ +import React from 'react'; +import { GlobalMenu } from '.'; + +export default { + title: 'Components/Multichain/GlobalMenu', + component: GlobalMenu, + argTypes: { + closeMenu: { + action: 'closeMenu', + }, + anchorElement: { + control: 'func', + }, + }, + args: { + closeMenu: () => console.log('Closing menu!'), + anchorElement: null, + }, +}; + +export const DefaultStory = (args) => ; +DefaultStory.storyName = 'Default'; diff --git a/ui/components/multichain/global-menu/global-menu.test.js b/ui/components/multichain/global-menu/global-menu.test.js new file mode 100644 index 000000000000..f3ed60fd344e --- /dev/null +++ b/ui/components/multichain/global-menu/global-menu.test.js @@ -0,0 +1,69 @@ +import React from 'react'; +import { renderWithProvider, fireEvent, waitFor } from '../../../../test/jest'; +import configureStore from '../../../store/store'; +import mockState from '../../../../test/data/mock-state.json'; +import { SUPPORT_LINK } from '../../../../shared/lib/ui-utils'; +import { GlobalMenu } from '.'; + +const render = () => { + const store = configureStore({ + metamask: { + ...mockState.metamask, + }, + }); + return renderWithProvider( + undefined} />, + store, + ); +}; + +const mockLockMetaMask = jest.fn(); +jest.mock('../../../store/actions', () => ({ + lockMetamask: () => mockLockMetaMask, +})); + +describe('AccountListItem', () => { + it('locks MetaMask when item is clicked', async () => { + render(); + fireEvent.click(document.querySelector('[data-testid="global-menu-lock"]')); + await waitFor(() => { + expect(mockLockMetaMask).toHaveBeenCalled(); + }); + }); + + it('opens the portfolio site when item is clicked', async () => { + global.platform = { openTab: jest.fn() }; + + const { getByTestId } = render(); + fireEvent.click(getByTestId('global-menu-portfolio')); + await waitFor(() => { + expect(global.platform.openTab).toHaveBeenCalledWith({ + url: `${process.env.PORTFOLIO_URL}?metamaskEntry=ext`, + }); + }); + }); + + it('opens the support site when item is clicked', async () => { + global.platform = { openTab: jest.fn() }; + + const { getByTestId } = render(); + fireEvent.click(getByTestId('global-menu-support')); + await waitFor(() => { + expect(global.platform.openTab).toHaveBeenCalledWith({ + url: SUPPORT_LINK, + }); + }); + }); + + it('expands metamask to tab when item is clicked', async () => { + global.platform = { openExtensionInBrowser: jest.fn() }; + + render(); + fireEvent.click( + document.querySelector('[data-testid="global-menu-expand"]'), + ); + await waitFor(() => { + expect(global.platform.openExtensionInBrowser).toHaveBeenCalled(); + }); + }); +}); diff --git a/ui/components/multichain/global-menu/index.js b/ui/components/multichain/global-menu/index.js new file mode 100644 index 000000000000..4850b1f92bfa --- /dev/null +++ b/ui/components/multichain/global-menu/index.js @@ -0,0 +1 @@ +export { GlobalMenu } from './global-menu'; diff --git a/ui/components/multichain/index.js b/ui/components/multichain/index.js index d34e427510d9..29562dc8373a 100644 --- a/ui/components/multichain/index.js +++ b/ui/components/multichain/index.js @@ -2,5 +2,6 @@ export { AccountListItem } from './account-list-item'; export { AccountListItemMenu } from './account-list-item-menu'; export { AccountListMenu } from './account-list-menu'; export { DetectedTokensBanner } from './detected-token-banner'; +export { GlobalMenu } from './global-menu'; export { MultichainImportTokenLink } from './multichain-import-token-link'; export { MultichainTokenListItem } from './multichain-token-list-item'; From 6be18552abf59d25a3e1576d4b2c26b13556ae24 Mon Sep 17 00:00:00 2001 From: Youmna Jaza Date: Wed, 29 Mar 2023 01:04:22 +0300 Subject: [PATCH 8/9] Updating Typography to Text (#17681) Co-authored-by: georgewrmarshall --- ui/components/app/add-network/add-network.js | 60 ++++++++++--------- .../confirm-page-container.component.js | 23 +++++-- 2 files changed, 49 insertions(+), 34 deletions(-) diff --git a/ui/components/app/add-network/add-network.js b/ui/components/app/add-network/add-network.js index 165359087340..816d1e92cf16 100644 --- a/ui/components/app/add-network/add-network.js +++ b/ui/components/app/add-network/add-network.js @@ -3,13 +3,11 @@ import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; import { I18nContext } from '../../../contexts/i18n'; import Box from '../../ui/box'; -import Typography from '../../ui/typography'; import { AlignItems, DISPLAY, FLEX_DIRECTION, - FONT_WEIGHT, - TypographyVariant, + TextVariant, JustifyContent, BorderRadius, BackgroundColor, @@ -38,7 +36,7 @@ import { FEATURED_RPCS } from '../../../../shared/constants/network'; import { ADD_NETWORK_ROUTE } from '../../../helpers/constants/routes'; import { getEnvironmentType } from '../../../../app/scripts/lib/util'; import ZENDESK_URLS from '../../../helpers/constants/zendesk-url'; -import { Icon, ICON_NAMES, ICON_SIZES } from '../../component-library'; +import { Icon, ICON_NAMES, ICON_SIZES, Text } from '../../component-library'; import { EVENT } from '../../../../shared/constants/metametrics'; const AddNetwork = () => { @@ -98,7 +96,7 @@ const AddNetwork = () => { - + {t('youHaveAddedAll', [ { : history.push(ADD_NETWORK_ROUTE); }} > - {t('addMoreNetworks')}. - + , ])} - + ) : ( @@ -144,19 +143,21 @@ const AddNetwork = () => { paddingBottom={2} className="add-network__header" > - {t('networks')} - + {' > '} - {t('addANetwork')} - + )} { marginBottom={1} className="add-network__main-container" > - {t('addFromAListOfPopularNetworks')} - - + {t('popularCustomNetworks')} - + {notExistingNetworkConfigurations.map((item, index) => ( { - {item.nickname} - + { : history.push(ADD_NETWORK_ROUTE); }} > - {t('addANetworkManually')} - + diff --git a/ui/components/app/confirm-page-container/confirm-page-container.component.js b/ui/components/app/confirm-page-container/confirm-page-container.component.js index 1943c964b145..aa12f0cf5c35 100644 --- a/ui/components/app/confirm-page-container/confirm-page-container.component.js +++ b/ui/components/app/confirm-page-container/confirm-page-container.component.js @@ -20,8 +20,11 @@ import EditGasFeePopover from '../edit-gas-fee-popover/edit-gas-fee-popover'; import EditGasPopover from '../edit-gas-popover'; import ErrorMessage from '../../ui/error-message'; import { INSUFFICIENT_FUNDS_ERROR_KEY } from '../../../helpers/constants/error-keys'; -import Typography from '../../ui/typography'; -import { TypographyVariant } from '../../../helpers/constants/design-system'; +import { Text } from '../../component-library'; +import { + TextVariant, + TEXT_ALIGN, +} from '../../../helpers/constants/design-system'; import NetworkAccountBalanceHeader from '../network-account-balance-header/network-account-balance-header'; import { fetchTokenBalance } from '../../../../shared/lib/token-util.ts'; @@ -233,7 +236,11 @@ const ConfirmPageContainer = (props) => { + {t('insufficientCurrencyBuyOrDeposit', [ nativeCurrency, networkName, @@ -256,14 +263,18 @@ const ConfirmPageContainer = (props) => { {t('buyAsset', [nativeCurrency])} , ])} - + ) : ( - + {t('insufficientCurrencyDeposit', [ nativeCurrency, networkName, ])} - + ) } useIcon From 8f65c268b43b602af84485b225269c5861f9211d Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Wed, 29 Mar 2023 14:00:34 +0800 Subject: [PATCH 9/9] Welcome onboarding unit test (#18248) --- .../onboarding-flow/welcome/welcome.test.js | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 ui/pages/onboarding-flow/welcome/welcome.test.js diff --git a/ui/pages/onboarding-flow/welcome/welcome.test.js b/ui/pages/onboarding-flow/welcome/welcome.test.js new file mode 100644 index 000000000000..8cd699012ff7 --- /dev/null +++ b/ui/pages/onboarding-flow/welcome/welcome.test.js @@ -0,0 +1,101 @@ +import React from 'react'; +import { fireEvent, screen } from '@testing-library/react'; +import configureMockStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import initializedMockState from '../../../../test/data/mock-state.json'; +import { renderWithProvider } from '../../../../test/lib/render-helpers'; +import { setFirstTimeFlowType } from '../../../store/actions'; +import { + ONBOARDING_METAMETRICS, + ONBOARDING_SECURE_YOUR_WALLET_ROUTE, + ONBOARDING_COMPLETION_ROUTE, +} from '../../../helpers/constants/routes'; +import OnboardingWelcome from './welcome'; + +const mockHistoryReplace = jest.fn(); +const mockHistoryPush = jest.fn(); + +jest.mock('../../../store/actions.ts', () => ({ + setFirstTimeFlowType: jest.fn().mockReturnValue( + jest.fn((type) => { + return type; + }), + ), +})); + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useHistory: () => ({ + push: mockHistoryPush, + replace: mockHistoryReplace, + }), +})); + +describe('Onboarding Welcome Component', () => { + const mockState = { + metamask: { + identities: {}, + selectedAddress: '', + }, + }; + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('Initialized State Conditionals with keyrings and firstTimeFlowType', () => { + it('should route to secure your wallet when keyring is present but not imported first time flow type', () => { + const mockStore = configureMockStore([thunk])(initializedMockState); + + renderWithProvider(, mockStore); + expect(mockHistoryReplace).toHaveBeenCalledWith( + ONBOARDING_SECURE_YOUR_WALLET_ROUTE, + ); + }); + + it('should route to completion when keyring is present and imported first time flow type', () => { + const importFirstTimeFlowState = { + ...initializedMockState, + metamask: { + ...initializedMockState.metamask, + firstTimeFlowType: 'import', + }, + }; + const mockStore = configureMockStore([thunk])(importFirstTimeFlowState); + + renderWithProvider(, mockStore); + expect(mockHistoryReplace).toHaveBeenCalledWith( + ONBOARDING_COMPLETION_ROUTE, + ); + }); + }); + + describe('Welcome Component', () => { + const mockStore = configureMockStore([thunk])(mockState); + + it('should render', () => { + renderWithProvider(, mockStore); + const onboardingWelcome = screen.queryByTestId('onboarding-welcome'); + expect(onboardingWelcome).toBeInTheDocument(); + }); + + it('should set first time flow to create and route to metametrics', () => { + renderWithProvider(, mockStore); + + const createWallet = screen.getByTestId('onboarding-create-wallet'); + fireEvent.click(createWallet); + + expect(setFirstTimeFlowType).toHaveBeenCalledWith('create'); + }); + + it('should set first time flow to import and route to metametrics', () => { + renderWithProvider(, mockStore); + + const createWallet = screen.getByTestId('onboarding-import-wallet'); + fireEvent.click(createWallet); + + expect(setFirstTimeFlowType).toHaveBeenCalledWith('import'); + expect(mockHistoryPush).toHaveBeenCalledWith(ONBOARDING_METAMETRICS); + }); + }); +});