1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Merge pull request #18102 from MetaMask/fix/599-fix-theme-issues-of-desktop-pairing-page-in-the-extension-ui

fix: fix theme issues of desktop pairing page
This commit is contained in:
OGPoyraz 2023-03-29 10:58:17 +02:00 committed by GitHub
commit f32d71ba1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 417 additions and 301 deletions

View File

@ -121,12 +121,17 @@ export async function getErrorHtml(
}
///: BEGIN:ONLY_INCLUDE_IN(flask)
export const MMD_DOWNLOAD_LINK =
'https://github.com/MetaMask/metamask-desktop/releases';
function disableDesktop(backgroundConnection) {
backgroundConnection.disableDesktopError();
}
export function downloadDesktopApp() {
global.platform.openTab({ url: 'https://metamask.io/' });
global.platform.openTab({
url: MMD_DOWNLOAD_LINK,
});
}
export function downloadExtension() {
@ -139,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();
});
}

View File

@ -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);
});
});
});

View File

@ -3,16 +3,13 @@
exports[`Desktop Pairing page should render otp component 1`] = `
<div>
<div
class="page-container__content"
class="box box--margin-right-2 box--margin-left-2 box--display-flex box--flex-direction-column box--align-items-center"
>
<div
class="desktop-pairing"
>
<div>
<div
class="box box--margin-top-12 box--margin-right-6 box--margin-left-6 box--display-flex box--flex-direction-column box--align-items-center box--text-align-center"
class="box box--margin-top-8 box--margin-bottom-8 box--display-flex box--flex-direction-row box--justify-content-center"
>
<svg
class="desktop-pairing__icon"
fill="currentColor"
height="64"
viewBox="0 0 64 39"
@ -93,27 +90,21 @@ exports[`Desktop Pairing page should render otp component 1`] = `
/>
</svg>
</div>
</div>
<div
class="desktop-pairing__title"
<h3
align="center"
class="box mm-text mm-text--heading-md box--flex-direction-row box--color-text-default"
>
Pair with Desktop
</div>
<div
class="desktop-pairing__subtitle"
</h3>
<p
align="center"
class="box mm-text mm-text--body-md box--margin-top-2 box--flex-direction-row box--color-text-default"
>
Open your MetaMask Desktop and type this code
</div>
</div>
</p>
<div
class="desktop-pairing"
>
<div
class="desktop-pairing__clickable"
class="box desktop-pairing__clickable box--margin-top-6 box--margin-bottom-6 box--flex-direction-row"
data-testid="desktop-pairing-otp-content"
>
<div
class="box box--margin-right-6 box--margin-left-6 box--display-flex box--flex-direction-column box--align-items-center box--text-align-center"
>
<div
class="desktop-pairing__tooltip-wrapper"
@ -126,16 +117,20 @@ exports[`Desktop Pairing page should render otp component 1`] = `
style="display: inline;"
tabindex="0"
>
<p
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography desktop-pairing__otp typography--p typography--weight-normal typography--style-normal typography--align-center typography--color-text-default"
<h1
align="center"
class="box mm-text desktop-pairing__otp mm-text--display-md box--flex-direction-row box--color-text-default"
>
123456
</p>
</div>
</h1>
</div>
</div>
<div
class="box box--margin-top-4 box--margin-bottom-6 box--display-flex box--flex-direction-row box--justify-content-center box--align-items-center"
>
<p
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography desktop-pairing__countdown-timer typography--p typography--weight-normal typography--style-normal typography--align-center typography--color-text-default"
align="center"
class="box mm-text desktop-pairing__countdown-timer mm-text--body-md box--padding-2 box--flex-direction-row box--color-text-default box--background-color-background-default box--rounded-xl"
>
<span>
@ -149,20 +144,19 @@ exports[`Desktop Pairing page should render otp component 1`] = `
</span>
</p>
<div
class="desktop-pairing__description"
</div>
<p
align="center"
class="box mm-text mm-text--body-sm box--flex-direction-row box--color-text-default"
>
If the pairing is successful, extension will restart and you'll have to re-enter your password.
</div>
</div>
</p>
</div>
<div
class="desktop-pairing__buttons"
class="box box--flex-direction-row"
>
<button
class="button btn--rounded btn-primary"
role="button"
tabindex="0"
class="box mm-text mm-button-base mm-button-base--size-md mm-button-primary mm-text--body-md box--padding-right-4 box--padding-left-4 box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-primary-inverse box--background-color-primary-default box--rounded-pill"
>
Done
</button>

View File

@ -1,21 +1,22 @@
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 Typography from '../../components/ui/typography';
import { I18nContext } from '../../contexts/i18n';
import IconDesktopPairing from '../../components/ui/icon/icon-desktop-pairing';
import {
TEXT_ALIGN,
TypographyVariant,
TextVariant,
DISPLAY,
AlignItems,
FLEX_DIRECTION,
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, Button } from '../../components/component-library';
export default function DesktopPairingPage({
generateDesktopOtp,
@ -75,19 +76,14 @@ export default function DesktopPairingPage({
const renderIcon = () => {
return (
<div>
<Box
display={DISPLAY.FLEX}
alignItems={AlignItems.center}
textAlign={TEXT_ALIGN.CENTER}
flexDirection={FLEX_DIRECTION.COLUMN}
marginLeft={6}
marginRight={6}
marginTop={12}
justifyContent={JustifyContent.center}
marginTop={8}
marginBottom={8}
>
<IconDesktopPairing size={64} />
<IconDesktopPairing className="desktop-pairing__icon" size={64} />
</Box>
</div>
);
};
@ -104,81 +100,94 @@ export default function DesktopPairingPage({
hideLoadingIndication();
return (
<div
<>
<Text variant={TextVariant.headingMd} align={TEXT_ALIGN.CENTER}>
{t('desktopPageTitle')}
</Text>
<Text marginTop={2} align={TEXT_ALIGN.CENTER}>
{t('desktopPageSubTitle')}
</Text>
<Box
marginBottom={6}
marginTop={6}
className="desktop-pairing__clickable"
onClick={() => {
handleCopy(otp);
}}
data-testid="desktop-pairing-otp-content"
>
<Box
display={DISPLAY.FLEX}
alignItems={AlignItems.center}
textAlign={TEXT_ALIGN.CENTER}
flexDirection={FLEX_DIRECTION.COLUMN}
marginLeft={6}
marginRight={6}
>
<Tooltip
wrapperClassName="desktop-pairing__tooltip-wrapper"
position="top"
title={copied ? t('copiedExclamation') : t('copyToClipboard')}
>
<Typography
<Text
align={TEXT_ALIGN.CENTER}
variant={TextVariant.displayMd}
className="desktop-pairing__otp"
>
{otp}
</Typography>
</Text>
</Tooltip>
</Box>
<Typography
variant={TypographyVariant.paragraph}
align={TEXT_ALIGN.CENTER}
<Box
display={DISPLAY.FLEX}
alignItems={AlignItems.center}
justifyContent={JustifyContent.center}
marginTop={4}
marginBottom={6}
>
<Text
className="desktop-pairing__countdown-timer"
variant={TextVariant.paragraph}
align={TEXT_ALIGN.CENTER}
backgroundColor={BackgroundColor.backgroundDefault}
borderRadius={BorderRadius.XL}
padding={2}
>
{t('desktopPairingExpireMessage', [
<span className="desktop-pairing__countdown-timer-seconds" key={1}>
<span
className="desktop-pairing__countdown-timer-seconds"
key={1}
>
{getExpireDuration()}
</span>,
])}
</Typography>
<div className="desktop-pairing__description">
</Text>
</Box>
<Text align={TEXT_ALIGN.CENTER} variant={TextVariant.bodySm}>
{t('desktopPageDescription')}
</div>
</div>
</Text>
</Box>
</>
);
};
const renderFooter = () => {
return (
<div className="desktop-pairing__buttons">
<Box>
<Button
type="primary"
rounded
onClick={() => {
goBack();
}}
>
{t('done')}
</Button>
</div>
</Box>
);
};
return (
<div className="page-container__content">
<div className="desktop-pairing">
<Box
display={DISPLAY.FLEX}
flexDirection="column"
alignItems={AlignItems.center}
marginLeft={2}
marginRight={2}
>
{renderIcon()}
<div className="desktop-pairing__title">{t('desktopPageTitle')}</div>
<div className="desktop-pairing__subtitle">
{t('desktopPageSubTitle')}
</div>
</div>
<div className="desktop-pairing">{renderContent()}</div>
{renderContent()}
{renderFooter()}
</div>
</Box>
);
}

View File

@ -0,0 +1,30 @@
import React from 'react';
import DesktopPairingPage from './desktop-pairing.component';
export default {
title: 'Pages/DesktopPairingPage',
component: DesktopPairingPage,
argTypes: {
showLoadingIndication: {
action: 'showLoadingIndication',
},
hideLoadingIndication: {
action: 'hideLoadingIndication',
},
generateDesktopOtp: {
action: 'generateDesktopOtp',
},
},
args: {
mostRecentOverviewPage: '/',
},
};
export const DefaultStory = (args) => {
const generateDesktopOtp = async () => Promise.resolve('123456');
return (
<DesktopPairingPage {...args} generateDesktopOtp={generateDesktopOtp} />
);
};
DefaultStory.storyName = 'Default';

View File

@ -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(<DesktopPairingPage />, 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(<DesktopPairingPage />, 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);
});
});
});

View File

@ -1,128 +1,28 @@
.desktop-pairing {
display: flex;
flex-flow: column;
align-items: center;
padding: 0 30px 0;
&__countdown-timer {
background: #f2f3f4;
border-radius: 15.5px;
padding: 7px 0 7px 0;
margin: 0 32px 0 32px;
font-size: 12px;
}
&__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;
min-width: 180px;
}
&__otp {
font-style: normal;
font-weight: 500;
font-size: 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 {
&__clickable: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;
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-icon-default);
}
}
}