mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-22 09:23:21 +01:00
Add friendly error handling when background throws an error before listening for connection (#14461)
* When background port closes, UI should display a user friendly error. Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Remove console.log Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> A couple of fixes 1. Use timeout in metaRPCClientFactory to check if UI can't communicate with bg 2. Refactor locale setup 3. Fixed wording/capitalization 4. Fix locales usage so that linting works 5. Refactor CSS Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> do not simulate errorwq Refactor loading css Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Remove the onDisconnect event handler in ui as this is handled in metarpcclientfactory Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Do not throw in bg Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Fix PR comments Remove unused message 'failedToLoadMessage' Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Move usage of locales to shared/** so that linter can see it. Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Do not simulate error. Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> metarpc can handle multiple requests, responseHandled should be a map. Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> reload metamask button on critical error Use metamask state (if available) to the locale, else read locale files manually. Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> use constant and numeric separator Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> refactor error utils remove error simulation Memoize setupLocale function Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> test cases Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Do not simulate error Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> 1. store should be metamask state 2. code refactorings. Tests: mock setupLocale Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Mock fetchLocale instead Test setup locale Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> UI/CSS changes. Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> Do not simulate failure Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> * spell MetaMask correctly Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> * Rename state to mockStore Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> * we should clean up this.responseHandled[id] in the error case. Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> * Fixed PR comments. Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com> * clean up response handled. Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>
This commit is contained in:
parent
2e1a37b47d
commit
4fa4930c8a
12
app/_locales/en/messages.json
generated
12
app/_locales/en/messages.json
generated
@ -2652,6 +2652,9 @@
|
||||
"resetWalletWarning": {
|
||||
"message": "Make sure you’re using the correct Secret Recovery Phrase before proceeding. You will not be able to undo this."
|
||||
},
|
||||
"restartMetamask": {
|
||||
"message": "Restart MetaMask"
|
||||
},
|
||||
"restore": {
|
||||
"message": "Restore"
|
||||
},
|
||||
@ -2836,6 +2839,9 @@
|
||||
"sendAmount": {
|
||||
"message": "Send Amount"
|
||||
},
|
||||
"sendBugReport": {
|
||||
"message": "Send us a bug report."
|
||||
},
|
||||
"sendSpecifiedTokens": {
|
||||
"message": "Send $1",
|
||||
"description": "Symbol of the specified token"
|
||||
@ -3109,6 +3115,9 @@
|
||||
"message": "Connect your wallet directly to your computer. Unlock your Ledger and open the Ethereum app. For more on using your hardware wallet device, $1.",
|
||||
"description": "$1 represents the `hardwareWalletSupportLinkConversion` localization key"
|
||||
},
|
||||
"stillGettingMessage": {
|
||||
"message": "Still getting this message?"
|
||||
},
|
||||
"storePhrase": {
|
||||
"message": "Store this phrase in a password manager like 1Password."
|
||||
},
|
||||
@ -3802,6 +3811,9 @@
|
||||
"message": "We had trouble connecting to your $1, try reviewing $2 and try again.",
|
||||
"description": "$1 is the wallet device name; $2 is a link to wallet connection guide"
|
||||
},
|
||||
"troubleStarting": {
|
||||
"message": "MetaMask had trouble starting. This error could be intermittent, so try restarting the extension."
|
||||
},
|
||||
"troubleTokenBalances": {
|
||||
"message": "We had trouble loading your token balances. You can view them ",
|
||||
"description": "Followed by a link (here) to view token balances"
|
||||
|
@ -8,7 +8,10 @@
|
||||
<link rel="stylesheet" type="text/css" href="./index-rtl.css" title="rtl" disabled>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app-content"><div id="app-loader">Loading...</div></div>
|
||||
<div id="app-content">
|
||||
<img class="loading-logo" src="./images/logo/metamask-fox.svg" alt="" />
|
||||
<img class="loading-spinner" src="./images/spinner.gif" alt="" />
|
||||
</div>
|
||||
<div id="popover-content"></div>
|
||||
<script src="./globalthis.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="./sentry-install.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
@ -8,7 +8,10 @@
|
||||
<link rel="stylesheet" type="text/css" href="./index-rtl.css" title="rtl" disabled>
|
||||
</head>
|
||||
<body style="width:357px; height:600px;">
|
||||
<div id="app-content"></div>
|
||||
<div id="app-content">
|
||||
<img class="loading-logo" src="./images/logo/metamask-fox.svg" alt="" />
|
||||
<img class="loading-spinner" src="./images/spinner.gif" alt="" />
|
||||
</div>
|
||||
<div id="popover-content"></div>
|
||||
<script src="./globalthis.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="./sentry-install.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { EthereumRpcError } from 'eth-rpc-errors';
|
||||
import SafeEventEmitter from 'safe-event-emitter';
|
||||
import createRandomId from '../../../shared/modules/random-id';
|
||||
import { TEN_SECONDS_IN_MILLISECONDS } from '../../../ui/helpers/constants/critical-error';
|
||||
|
||||
class MetaRPCClient {
|
||||
constructor(connectionStream) {
|
||||
@ -10,6 +11,23 @@ class MetaRPCClient {
|
||||
this.requests = new Map();
|
||||
this.connectionStream.on('data', this.handleResponse.bind(this));
|
||||
this.connectionStream.on('end', this.close.bind(this));
|
||||
this.responseHandled = {};
|
||||
}
|
||||
|
||||
send(id, payload, cb) {
|
||||
this.requests.set(id, cb);
|
||||
this.connectionStream.write(payload);
|
||||
this.responseHandled[id] = false;
|
||||
setTimeout(() => {
|
||||
if (!this.responseHandled[id] && cb) {
|
||||
delete this.responseHandled[id];
|
||||
return cb(new Error('No response from RPC'), null);
|
||||
}
|
||||
|
||||
delete this.responseHandled[id];
|
||||
// needed for linter to pass
|
||||
return true;
|
||||
}, TEN_SECONDS_IN_MILLISECONDS);
|
||||
}
|
||||
|
||||
onNotification(handler) {
|
||||
@ -34,6 +52,8 @@ class MetaRPCClient {
|
||||
const isNotification = id === undefined && error === undefined;
|
||||
const cb = this.requests.get(id);
|
||||
|
||||
this.responseHandled[id] = true;
|
||||
|
||||
if (method && params && !isNotification) {
|
||||
// dont handle server-side to client-side requests
|
||||
return;
|
||||
@ -79,14 +99,13 @@ const metaRPCClientFactory = (connectionStream) => {
|
||||
const cb = p[p.length - 1];
|
||||
const params = p.slice(0, -1);
|
||||
const id = createRandomId();
|
||||
|
||||
object.requests.set(id, cb);
|
||||
object.connectionStream.write({
|
||||
const payload = {
|
||||
jsonrpc: '2.0',
|
||||
method: property,
|
||||
params,
|
||||
id,
|
||||
});
|
||||
};
|
||||
object.send(id, payload, cb);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@ -131,4 +131,21 @@ describe('metaRPCClientFactory', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to handle no message within TIMEOUT secs', async () => {
|
||||
jest.useFakeTimers();
|
||||
const streamTest = createThoughStream();
|
||||
const metaRPCClient = metaRPCClientFactory(streamTest);
|
||||
|
||||
const errorPromise = new Promise((_resolve, reject) =>
|
||||
metaRPCClient.foo('bad', (error, _) => {
|
||||
reject(error);
|
||||
}),
|
||||
);
|
||||
|
||||
jest.runOnlyPendingTimers();
|
||||
await expect(errorPromise).rejects.toThrow('No response from RPC');
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
});
|
||||
|
@ -17,6 +17,8 @@ import {
|
||||
ENVIRONMENT_TYPE_POPUP,
|
||||
} from '../../shared/constants/app';
|
||||
import { isManifestV3 } from '../../shared/modules/mv3.utils';
|
||||
import { SUPPORT_LINK } from '../../ui/helpers/constants/common';
|
||||
import { getErrorHtml } from '../../ui/helpers/utils/error-utils';
|
||||
import ExtensionPlatform from './platforms/extension';
|
||||
import { setupMultiplex } from './lib/stream-utils';
|
||||
import { getEnvironmentType } from './lib/util';
|
||||
@ -25,6 +27,21 @@ import metaRPCClientFactory from './lib/metaRPCClientFactory';
|
||||
start().catch(log.error);
|
||||
|
||||
async function start() {
|
||||
async function displayCriticalError(container, err, metamaskState) {
|
||||
const html = await getErrorHtml(SUPPORT_LINK, metamaskState);
|
||||
|
||||
container.innerHTML = html;
|
||||
|
||||
const button = document.getElementById('critical-error-button');
|
||||
|
||||
button.addEventListener('click', (_) => {
|
||||
browser.runtime.reload();
|
||||
});
|
||||
|
||||
log.error(err.stack);
|
||||
throw err;
|
||||
}
|
||||
|
||||
// create platform global
|
||||
global.platform = new ExtensionPlatform();
|
||||
|
||||
@ -33,6 +50,7 @@ async function start() {
|
||||
|
||||
// setup stream to background
|
||||
const extensionPort = browser.runtime.connect({ name: windowType });
|
||||
|
||||
const connectionStream = new PortStream(extensionPort);
|
||||
|
||||
const activeTab = await queryCurrentActiveTab(windowType);
|
||||
@ -51,19 +69,12 @@ async function start() {
|
||||
initializeUiWithTab(activeTab);
|
||||
}
|
||||
|
||||
function displayCriticalError(container, err) {
|
||||
container.innerHTML =
|
||||
'<div class="critical-error">The MetaMask app failed to load: please open and close MetaMask again to restart.</div>';
|
||||
container.style.height = '80px';
|
||||
log.error(err.stack);
|
||||
throw err;
|
||||
}
|
||||
|
||||
function initializeUiWithTab(tab) {
|
||||
const container = document.getElementById('app-content');
|
||||
initializeUi(tab, container, connectionStream, (err, store) => {
|
||||
if (err) {
|
||||
displayCriticalError(container, err);
|
||||
// if there's an error, store will be = metamaskState
|
||||
displayCriticalError(container, err, store);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -104,7 +115,7 @@ async function queryCurrentActiveTab(windowType) {
|
||||
function initializeUi(activeTab, container, connectionStream, cb) {
|
||||
connectToAccountManager(connectionStream, (err, backgroundConnection) => {
|
||||
if (err) {
|
||||
cb(err);
|
||||
cb(err, null);
|
||||
return;
|
||||
}
|
||||
|
||||
|
44
ui/css/errors.scss
Normal file
44
ui/css/errors.scss
Normal file
@ -0,0 +1,44 @@
|
||||
.critical-error {
|
||||
padding: 16px;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
|
||||
&__alert {
|
||||
color: var(--color-text-default);
|
||||
background-color: var(--color-error-muted);
|
||||
border: 1px solid var(--color-error-default);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 16px;
|
||||
|
||||
&__message {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
&__button {
|
||||
height: 40px;
|
||||
border-radius: 20px;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
background-color: var(--color-primary-default);
|
||||
color: var(--color-primary-inverse);
|
||||
border: 1px solid var(--color-primary-default);
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
&__paragraph {
|
||||
color: var(--color-text-default);
|
||||
text-align: center;
|
||||
|
||||
&__link {
|
||||
color: var(--color-primary-default);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-primary-alternative);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,8 @@
|
||||
@import '../components/app/app-components';
|
||||
@import '../components/ui/ui-components';
|
||||
@import '../pages/pages';
|
||||
@import './errors.scss';
|
||||
@import './loading.scss';
|
||||
|
||||
/*
|
||||
ITCSS
|
||||
|
13
ui/css/loading.scss
Normal file
13
ui/css/loading.scss
Normal file
@ -0,0 +1,13 @@
|
||||
.loading-logo {
|
||||
width: 10rem;
|
||||
height: 10rem;
|
||||
align-self: center;
|
||||
margin: 10rem 0 0 0;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
align-self: center;
|
||||
margin-top: 1rem;
|
||||
}
|
1
ui/helpers/constants/critical-error.js
Normal file
1
ui/helpers/constants/critical-error.js
Normal file
@ -0,0 +1 @@
|
||||
export const TEN_SECONDS_IN_MILLISECONDS = 10_000;
|
55
ui/helpers/utils/error-utils.js
Normal file
55
ui/helpers/utils/error-utils.js
Normal file
@ -0,0 +1,55 @@
|
||||
import getFirstPreferredLangCode from '../../../app/scripts/lib/get-first-preferred-lang-code';
|
||||
import { setupLocale } from '../..';
|
||||
import switchDirection from './switch-direction';
|
||||
|
||||
const getLocaleContext = (currentLocaleMessages, enLocaleMessages) => {
|
||||
return (key) => {
|
||||
let message = currentLocaleMessages[key]?.message;
|
||||
if (!message && enLocaleMessages[key]) {
|
||||
message = enLocaleMessages[key].message;
|
||||
}
|
||||
return message;
|
||||
};
|
||||
};
|
||||
|
||||
export async function getErrorHtml(supportLink, metamaskState) {
|
||||
let response, preferredLocale;
|
||||
if (metamaskState?.currentLocale) {
|
||||
preferredLocale = metamaskState.currentLocale;
|
||||
response = await setupLocale(metamaskState.currentLocale);
|
||||
} else {
|
||||
preferredLocale = await getFirstPreferredLangCode();
|
||||
response = await setupLocale(preferredLocale);
|
||||
}
|
||||
|
||||
const textDirection = ['ar', 'dv', 'fa', 'he', 'ku'].includes(preferredLocale)
|
||||
? 'rtl'
|
||||
: 'auto';
|
||||
|
||||
switchDirection(textDirection);
|
||||
const { currentLocaleMessages, enLocaleMessages } = response;
|
||||
const t = getLocaleContext(currentLocaleMessages, enLocaleMessages);
|
||||
|
||||
return `
|
||||
<div class="critical-error">
|
||||
<div class="critical-error__alert">
|
||||
<p class="critical-error__alert__message">
|
||||
${t('troubleStarting')}
|
||||
</p>
|
||||
<button id='critical-error-button' class="critical-error__alert__button">
|
||||
${t('restartMetamask')}
|
||||
</button>
|
||||
</div>
|
||||
<p class="critical-error__paragraph">
|
||||
${t('stillGettingMessage')}
|
||||
<a
|
||||
href=${supportLink}
|
||||
class="critical-error__paragraph__link"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
${t('sendBugReport')}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
49
ui/helpers/utils/error-utils.test.js
Normal file
49
ui/helpers/utils/error-utils.test.js
Normal file
@ -0,0 +1,49 @@
|
||||
import { SUPPORT_LINK } from '../constants/common';
|
||||
import { getErrorHtml } from './error-utils';
|
||||
import { fetchLocale } from './i18n-helper';
|
||||
|
||||
jest.mock('./i18n-helper', () => ({
|
||||
fetchLocale: jest.fn(),
|
||||
loadRelativeTimeFormatLocaleData: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('Error utils Tests', () => {
|
||||
it('should get error html', async () => {
|
||||
const mockStore = {
|
||||
localeMessages: {
|
||||
current: {
|
||||
troubleStarting: {
|
||||
message:
|
||||
'MetaMask had trouble starting. This error could be intermittent, so try restarting the extension.',
|
||||
},
|
||||
restartMetamask: {
|
||||
message: 'Restart MetaMask',
|
||||
},
|
||||
stillGettingMessage: {
|
||||
message: 'Still getting this message?',
|
||||
},
|
||||
sendBugReport: {
|
||||
message: 'Send us a bug report.',
|
||||
},
|
||||
},
|
||||
},
|
||||
metamask: {
|
||||
currentLocale: 'en',
|
||||
},
|
||||
};
|
||||
|
||||
fetchLocale.mockReturnValue(mockStore.localeMessages.current);
|
||||
const errorHtml = await getErrorHtml(SUPPORT_LINK, mockStore.metamask);
|
||||
const currentLocale = mockStore.localeMessages.current;
|
||||
const troubleStartingMessage = currentLocale.troubleStarting.message;
|
||||
const restartMetamaskMessage = currentLocale.restartMetamask.message;
|
||||
const stillGettingMessageMessage =
|
||||
currentLocale.stillGettingMessage.message;
|
||||
const sendBugReportMessage = currentLocale.sendBugReport.message;
|
||||
|
||||
expect(errorHtml).toContain(troubleStartingMessage);
|
||||
expect(errorHtml).toContain(restartMetamaskMessage);
|
||||
expect(errorHtml).toContain(stillGettingMessageMessage);
|
||||
expect(errorHtml).toContain(sendBugReportMessage);
|
||||
});
|
||||
});
|
32
ui/index.js
32
ui/index.js
@ -1,6 +1,6 @@
|
||||
import copyToClipboard from 'copy-to-clipboard';
|
||||
import log from 'loglevel';
|
||||
import { clone } from 'lodash';
|
||||
import { clone, memoize } from 'lodash';
|
||||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import browser from 'webextension-polyfill';
|
||||
@ -36,7 +36,7 @@ export default function launchMetamaskUi(opts, cb) {
|
||||
// check if we are unlocked first
|
||||
backgroundConnection.getState(function (err, metamaskState) {
|
||||
if (err) {
|
||||
cb(err);
|
||||
cb(err, metamaskState);
|
||||
return;
|
||||
}
|
||||
startApp(metamaskState, backgroundConnection, opts).then((store) => {
|
||||
@ -46,21 +46,31 @@ export default function launchMetamaskUi(opts, cb) {
|
||||
});
|
||||
}
|
||||
|
||||
const _setupLocale = async (currentLocale) => {
|
||||
const currentLocaleMessages = currentLocale
|
||||
? await fetchLocale(currentLocale)
|
||||
: {};
|
||||
const enLocaleMessages = await fetchLocale('en');
|
||||
|
||||
await loadRelativeTimeFormatLocaleData('en');
|
||||
if (currentLocale) {
|
||||
await loadRelativeTimeFormatLocaleData(currentLocale);
|
||||
}
|
||||
|
||||
return { currentLocaleMessages, enLocaleMessages };
|
||||
};
|
||||
|
||||
export const setupLocale = memoize(_setupLocale);
|
||||
|
||||
async function startApp(metamaskState, backgroundConnection, opts) {
|
||||
// parse opts
|
||||
if (!metamaskState.featureFlags) {
|
||||
metamaskState.featureFlags = {};
|
||||
}
|
||||
|
||||
const currentLocaleMessages = metamaskState.currentLocale
|
||||
? await fetchLocale(metamaskState.currentLocale)
|
||||
: {};
|
||||
const enLocaleMessages = await fetchLocale('en');
|
||||
|
||||
await loadRelativeTimeFormatLocaleData('en');
|
||||
if (metamaskState.currentLocale) {
|
||||
await loadRelativeTimeFormatLocaleData(metamaskState.currentLocale);
|
||||
}
|
||||
const { currentLocaleMessages, enLocaleMessages } = await setupLocale(
|
||||
metamaskState.currentLocale,
|
||||
);
|
||||
|
||||
if (metamaskState.textDirection === 'rtl') {
|
||||
await switchDirection('rtl');
|
||||
|
70
ui/index.test.js
Normal file
70
ui/index.test.js
Normal file
@ -0,0 +1,70 @@
|
||||
import { setupLocale } from '.';
|
||||
|
||||
const enMessages = {
|
||||
troubleStarting: {
|
||||
message:
|
||||
'MetaMask had trouble starting. This error could be intermittent, so try restarting the extension.',
|
||||
},
|
||||
restartMetamask: {
|
||||
message: 'Restart MetaMask',
|
||||
},
|
||||
stillGettingMessage: {
|
||||
message: 'Still getting this message?',
|
||||
},
|
||||
sendBugReport: {
|
||||
message: 'Send us a bug report.',
|
||||
},
|
||||
};
|
||||
|
||||
const esMessages = {
|
||||
troubleStarting: {
|
||||
message:
|
||||
'MetaMask tuvo problemas para iniciarse. Este error podría ser intermitente, así que intente reiniciar la extensión.',
|
||||
},
|
||||
restartMetamask: {
|
||||
message: 'Reiniciar metamáscara',
|
||||
},
|
||||
sendBugReport: {
|
||||
message: 'Envíenos un informe de errores.',
|
||||
},
|
||||
};
|
||||
|
||||
jest.mock('./helpers/utils/i18n-helper', () => ({
|
||||
fetchLocale: jest.fn((locale) => (locale === 'en' ? enMessages : esMessages)),
|
||||
loadRelativeTimeFormatLocaleData: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('Index Tests', () => {
|
||||
it('should get locale messages by calling setupLocale', async () => {
|
||||
let result = await setupLocale('en');
|
||||
const { currentLocaleMessages: clm, enLocaleMessages: elm } = result;
|
||||
expect(clm).toBeDefined();
|
||||
expect(elm).toBeDefined();
|
||||
expect(clm.troubleStarting).toStrictEqual(enMessages.troubleStarting);
|
||||
|
||||
expect(clm.restartMetamask).toStrictEqual(enMessages.restartMetamask);
|
||||
|
||||
expect(clm.stillGettingMessage).toStrictEqual(
|
||||
enMessages.stillGettingMessage,
|
||||
);
|
||||
|
||||
expect(clm.sendBugReport).toStrictEqual(enMessages.sendBugReport);
|
||||
|
||||
result = await setupLocale('es');
|
||||
|
||||
const { currentLocaleMessages: clm2, enLocaleMessages: elm2 } = result;
|
||||
expect(clm2).toBeDefined();
|
||||
expect(elm2).toBeDefined();
|
||||
|
||||
expect(clm2.troubleStarting).toStrictEqual(esMessages.troubleStarting);
|
||||
|
||||
expect(clm2.restartMetamask).toStrictEqual(esMessages.restartMetamask);
|
||||
|
||||
expect(clm2.stillGettingMessage).toBeUndefined();
|
||||
expect(elm2.stillGettingMessage).toStrictEqual(
|
||||
enMessages.stillGettingMessage,
|
||||
);
|
||||
|
||||
expect(clm2.sendBugReport).toStrictEqual(esMessages.sendBugReport);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user