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/5] 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/5] 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/5] 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/5] 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" >