From ad7d38c0dc206074379c813b307ed9350c7efeb0 Mon Sep 17 00:00:00 2001 From: Jakub Stasiak Date: Wed, 11 Apr 2018 18:32:27 +0200 Subject: [PATCH 01/31] Update: allow other extension to connect --- app/manifest.json | 3 ++- app/scripts/background.js | 14 ++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/manifest.json b/app/manifest.json index dc46f1ca4..950bab2f1 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -67,6 +67,7 @@ "externally_connectable": { "matches": [ "https://metamask.io/*" - ] + ], + "ids": ["*"] } } \ No newline at end of file diff --git a/app/scripts/background.js b/app/scripts/background.js index 6550e8944..6296eaa21 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -197,6 +197,7 @@ function setupController (initState, initLangCode) { // connect to other contexts // extension.runtime.onConnect.addListener(connectRemote) + extension.runtime.onConnectExternal.addListener(connectExternal) const metamaskInternalProcessHash = { [ENVIRONMENT_TYPE_POPUP]: true, @@ -211,9 +212,9 @@ function setupController (initState, initLangCode) { function connectRemote (remotePort) { const processName = remotePort.name const isMetaMaskInternalProcess = metamaskInternalProcessHash[processName] - const portStream = new PortStream(remotePort) if (isMetaMaskInternalProcess) { + const portStream = new PortStream(remotePort) // communication with popup controller.isClientOpen = true controller.setupTrustedCommunication(portStream, 'MetaMask') @@ -246,12 +247,17 @@ function setupController (initState, initLangCode) { }) } } else { - // communication with page - const originDomain = urlUtil.parse(remotePort.sender.url).hostname - controller.setupUntrustedCommunication(portStream, originDomain) + connectExternal(remotePort) } } + // communication with page or other extension + function connectExternal(remotePort) { + const originDomain = urlUtil.parse(remotePort.sender.url).hostname + const portStream = new PortStream(remotePort) + controller.setupUntrustedCommunication(portStream, originDomain) + } + // // User Interface setup // From 7eb735651bcd1c3a4ef2b4da1be5d7444e282b44 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Sun, 29 Apr 2018 16:00:13 -0700 Subject: [PATCH 02/31] transactions - run event emitters outside context of _setTxStatus --- .../controllers/transactions/tx-state-manager.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js index 53428c333..17938e70f 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.js +++ b/app/scripts/controllers/transactions/tx-state-manager.js @@ -398,13 +398,15 @@ class TransactionStateManager extends EventEmitter { _setTxStatus (txId, status) { const txMeta = this.getTx(txId) txMeta.status = status - this.emit(`${txMeta.id}:${status}`, txId) - this.emit(`tx:status-update`, txId, status) - if (['submitted', 'rejected', 'failed'].includes(status)) { - this.emit(`${txMeta.id}:finished`, txMeta) - } - this.updateTx(txMeta, `txStateManager: setting status to ${status}`) - this.emit('update:badge') + setTimeout(() => { + this.updateTx(txMeta, `txStateManager: setting status to ${status}`) + this.emit(`${txMeta.id}:${status}`, txId) + this.emit(`tx:status-update`, txId, status) + if (['submitted', 'rejected', 'failed'].includes(status)) { + this.emit(`${txMeta.id}:finished`, txMeta) + } + this.emit('update:badge') + }) } /** From 706647785cee23d177d646c3a06f5dc2a2586feb Mon Sep 17 00:00:00 2001 From: frankiebee Date: Sun, 29 Apr 2018 16:33:46 -0700 Subject: [PATCH 03/31] log emitter errors --- .../controllers/transactions/tx-state-manager.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js index 17938e70f..28b6d6d4f 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.js +++ b/app/scripts/controllers/transactions/tx-state-manager.js @@ -399,13 +399,17 @@ class TransactionStateManager extends EventEmitter { const txMeta = this.getTx(txId) txMeta.status = status setTimeout(() => { - this.updateTx(txMeta, `txStateManager: setting status to ${status}`) - this.emit(`${txMeta.id}:${status}`, txId) - this.emit(`tx:status-update`, txId, status) - if (['submitted', 'rejected', 'failed'].includes(status)) { - this.emit(`${txMeta.id}:finished`, txMeta) + try { + this.updateTx(txMeta, `txStateManager: setting status to ${status}`) + this.emit(`${txMeta.id}:${status}`, txId) + this.emit(`tx:status-update`, txId, status) + if (['submitted', 'rejected', 'failed'].includes(status)) { + this.emit(`${txMeta.id}:finished`, txMeta) + } + this.emit('update:badge') + } catch (error) { + log.error(error) } - this.emit('update:badge') }) } From 98ae853b6c67bce137df00c2527e5ece25f1129e Mon Sep 17 00:00:00 2001 From: frankiebee Date: Mon, 30 Apr 2018 09:57:36 -0700 Subject: [PATCH 04/31] require log --- app/scripts/controllers/transactions/tx-state-manager.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js index 28b6d6d4f..f05c7d095 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.js +++ b/app/scripts/controllers/transactions/tx-state-manager.js @@ -2,6 +2,7 @@ const extend = require('xtend') const EventEmitter = require('events') const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') +const log = require('loglevel') const txStateHistoryHelper = require('./lib/tx-state-history-helper') const createId = require('../../lib/random-id') const { getFinalStates } = require('./lib/util') From 477063a5a0e9f218af2a49886c44a9bc153c13d3 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 30 Apr 2018 13:29:22 -0700 Subject: [PATCH 05/31] Version 4.6.1 --- CHANGELOG.md | 9 ++++++++- app/manifest.json | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bb50fd5b..e4dd7ae98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## Current Master +## 4.6.1 Mon Apr 30 2018 + +- Fix bug where sending a transaction resulted in an infinite spinner +- Allow transactions with a 0 gwei gas price +- Handle encoding errors in ERC20 symbol + digits +- Fix ShapeShift forms (new + old ui) +- Fix sourcemaps + ## 4.6.0 Thu Apr 26 2018 - Correctly format currency conversion for locally selected preferred currency. @@ -9,7 +17,6 @@ - Fetch token prices based on contract address, not symbol - Fix bug that prevents setting language locale in settings. - Show checksum addresses throughout the UI -- Allow transactions with a 0 gwei gas price ## 4.5.5 Fri Apr 06 2018 diff --git a/app/manifest.json b/app/manifest.json index 3e5eed205..8a14323f0 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "4.6.0", + "version": "4.6.1", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", From f5cefbbeda4c0ad6ad578a56e0e87d9e4171cc18 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Thu, 24 May 2018 10:54:51 -0230 Subject: [PATCH 06/31] fix: Why does npm insist on reordering the lockfile? --- package-lock.json | 180 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 170 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index a1fdf0059..e3e1b4eec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1934,6 +1934,12 @@ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, + "arch": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.1.0.tgz", + "integrity": "sha1-NhOqRhSQZLPB8GB5Gb8dR4boKIk=", + "dev": true + }, "archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", @@ -3793,6 +3799,12 @@ "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", "dev": true }, + "base64url": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", + "integrity": "sha1-6sFuA+oUOO/5Qj1puqNiYu0fcLs=", + "dev": true + }, "bcrypt-pbkdf": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", @@ -5141,6 +5153,33 @@ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" }, + "clipboardy": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-1.2.3.tgz", + "integrity": "sha512-2WNImOvCRe6r63Gk9pShfkwXsVtKCroMAevIbiae021mS850UkWPbevxsBz3tnvjZIEGvlwaqCPsw+4ulzNgJA==", + "dev": true, + "requires": { + "arch": "2.1.0", + "execa": "0.8.0" + }, + "dependencies": { + "execa": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz", + "integrity": "sha1-2NdrvBtVIX7RkP1t1J08d07PyNo=", + "dev": true, + "requires": { + "cross-spawn": "5.1.0", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" + } + } + } + }, "cliui": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", @@ -8693,16 +8732,16 @@ "integrity": "sha1-x7kULEtZUJsziiBLYyiupA3Txk4=" }, "ethjs": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/ethjs/-/ethjs-0.3.6.tgz", - "integrity": "sha512-9ojnSkV5XXSM5vo0pKgZpE+SNBPxqSUN0dZmMP5dBZVFOYctRd9tfaZ80Jnde3M4JrfUhhkbG5QFvewitaAY7Q==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/ethjs/-/ethjs-0.4.0.tgz", + "integrity": "sha512-UnQeRMpQ+JETN2FviexEskUwByid+eO8rybjPnk2DNUzjUn0VKNrUbiCAud7Es6otDFwjUeOS58vMZwkZxIIog==", "requires": { "bn.js": "4.11.6", "ethjs-abi": "0.2.1", - "ethjs-contract": "0.1.9", - "ethjs-filter": "0.1.5", + "ethjs-contract": "0.2.3", + "ethjs-filter": "0.1.8", "ethjs-provider-http": "0.1.6", - "ethjs-query": "0.3.4", + "ethjs-query": "0.3.8", "ethjs-unit": "0.1.6", "ethjs-util": "0.1.3", "js-sha3": "0.5.5", @@ -8725,12 +8764,13 @@ } }, "ethjs-contract": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/ethjs-contract/-/ethjs-contract-0.1.9.tgz", - "integrity": "sha1-HCdmiWpW1H7B1tZhgpxJzDilUgo=", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/ethjs-contract/-/ethjs-contract-0.2.3.tgz", + "integrity": "sha512-fKsHm57wxwHrZhVlD8AHU2lC2G3c1fmvoEz15BpqIkuGWiTbjuvrQo2Avc+3EQpSsTFWNdyxC0h1WKRcn5kkyQ==", "requires": { + "babel-runtime": "6.26.0", "ethjs-abi": "0.2.0", - "ethjs-filter": "0.1.5", + "ethjs-filter": "0.1.8", "ethjs-util": "0.1.3", "js-sha3": "0.5.5" }, @@ -8747,6 +8787,30 @@ } } }, + "ethjs-filter": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/ethjs-filter/-/ethjs-filter-0.1.8.tgz", + "integrity": "sha512-qTDPskDL2UadHwjvM8A+WG9HwM4/FoSY3p3rMJORkHltYcAuiQZd2otzOYKcL5w2Q3sbAkW/E3yt/FPFL/AVXA==" + }, + "ethjs-query": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/ethjs-query/-/ethjs-query-0.3.8.tgz", + "integrity": "sha512-/J5JydqrOzU8O7VBOwZKUWXxHDGr46VqNjBCJgBVNNda+tv7Xc8Y2uJc6aMHHVbeN3YOQ7YRElgIc0q1CI02lQ==", + "requires": { + "babel-runtime": "6.26.0", + "ethjs-format": "0.2.7", + "ethjs-rpc": "0.2.0", + "promise-to-callback": "1.0.0" + } + }, + "ethjs-rpc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ethjs-rpc/-/ethjs-rpc-0.2.0.tgz", + "integrity": "sha512-RINulkNZTKnj4R/cjYYtYMnFFaBcVALzbtEJEONrrka8IeoarNB9Jbzn+2rT00Cv8y/CxAI+GgY1d0/i2iQeOg==", + "requires": { + "promise-to-callback": "1.0.0" + } + }, "ethjs-util": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.3.tgz", @@ -8895,6 +8959,35 @@ "resolved": "https://registry.npmjs.org/ethjs-filter/-/ethjs-filter-0.1.5.tgz", "integrity": "sha1-ARKvYBfCRnfjK4/esg5hlgGbdZg=" }, + "ethjs-format": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/ethjs-format/-/ethjs-format-0.2.7.tgz", + "integrity": "sha512-uNYAi+r3/mvR3xYu2AfSXx5teP4ovy9z2FrRsblU+h2logsaIKZPi9V3bn3V7wuRcnG0HZ3QydgZuVaRo06C4Q==", + "requires": { + "bn.js": "4.11.6", + "ethjs-schema": "0.2.1", + "ethjs-util": "0.1.3", + "is-hex-prefixed": "1.0.0", + "number-to-bn": "1.7.0", + "strip-hex-prefix": "1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=" + }, + "ethjs-util": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.3.tgz", + "integrity": "sha1-39XqSkANxeQhqInK9H4IGtp4u1U=", + "requires": { + "is-hex-prefixed": "1.0.0", + "strip-hex-prefix": "1.0.0" + } + } + } + }, "ethjs-provider-http": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/ethjs-provider-http/-/ethjs-provider-http-0.1.6.tgz", @@ -8956,6 +9049,11 @@ "resolved": "https://registry.npmjs.org/ethjs-rpc/-/ethjs-rpc-0.1.5.tgz", "integrity": "sha1-CZ4i8n3EwYtpeKSF/DaxsPeWkIA=" }, + "ethjs-schema": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ethjs-schema/-/ethjs-schema-0.2.1.tgz", + "integrity": "sha512-DXd8lwNrhT9sjsh/Vd2Z+4pfyGxhc0POVnLBUfwk5udtdoBzADyq+sK39dcb48+ZU+2VgtwHxtGWnLnCfmfW5g==" + }, "ethjs-unit": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz", @@ -11347,6 +11445,62 @@ } } }, + "gh-pages": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-1.1.0.tgz", + "integrity": "sha512-ZpDkeOVmIrN5mz+sBWDz5zmTqcbNJzI/updCwEv/7rrSdpTNlj1B5GhBqG7f4Q8p5sJOdnBV0SIqxJrxtZQ9FA==", + "dev": true, + "requires": { + "async": "2.6.0", + "base64url": "2.0.0", + "commander": "2.11.0", + "fs-extra": "4.0.3", + "globby": "6.1.0", + "graceful-fs": "4.1.11", + "rimraf": "2.6.2" + }, + "dependencies": { + "fs-extra": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "jsonfile": "4.0.0", + "universalify": "0.1.1" + } + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, "gifencoder": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/gifencoder/-/gifencoder-1.1.0.tgz", @@ -30195,6 +30349,12 @@ "unist-util-is": "2.1.1" } }, + "universalify": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", + "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=", + "dev": true + }, "unorm": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.4.1.tgz", From 715624227a12e4fdec2634319e4a619927986958 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Thu, 24 May 2018 22:47:26 -0230 Subject: [PATCH 07/31] Add basic e2e tests for the new UI --- package.json | 2 + test/e2e/beta/from-import-beta-ui.spec.js | 406 ++++++++++++++++++ test/e2e/beta/helpers.js | 55 +++ test/e2e/beta/metamask-beta-ui.spec.js | 491 ++++++++++++++++++++++ test/e2e/beta/run-all.sh | 10 + test/e2e/func.js | 11 +- 6 files changed, 973 insertions(+), 2 deletions(-) create mode 100644 test/e2e/beta/from-import-beta-ui.spec.js create mode 100644 test/e2e/beta/helpers.js create mode 100644 test/e2e/beta/metamask-beta-ui.spec.js create mode 100755 test/e2e/beta/run-all.sh diff --git a/package.json b/package.json index 372ffbfa5..5fbbabe80 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,9 @@ "test:integration": "npm run test:integration:build && npm run test:flat && npm run test:mascara", "test:integration:build": "gulp build:scss", "test:e2e:chrome": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:e2e:run:chrome'", + "test:e2e:chrome:beta": "SELENIUM_BROWSER=chrome test/e2e/beta/run-all.sh", "test:e2e:firefox": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:e2e:run:firefox'", + "test:e2e:firefox:beta": "SELENIUM_BROWSER=firefox test/e2e/beta/run-all.sh", "test:e2e:run:chrome": "SELENIUM_BROWSER=chrome mocha test/e2e/metamask.spec --bail --recursive", "test:e2e:run:firefox": "SELENIUM_BROWSER=firefox mocha test/e2e/metamask.spec --bail --recursive", "test:screens": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:screens:run'", diff --git a/test/e2e/beta/from-import-beta-ui.spec.js b/test/e2e/beta/from-import-beta-ui.spec.js new file mode 100644 index 000000000..e07d4a99e --- /dev/null +++ b/test/e2e/beta/from-import-beta-ui.spec.js @@ -0,0 +1,406 @@ +const path = require('path') +const assert = require('assert') +const webdriver = require('selenium-webdriver') +const { By, Key } = webdriver +const { + delay, + buildChromeWebDriver, + buildFirefoxWebdriver, + installWebExt, + getExtensionIdChrome, + getExtensionIdFirefox, +} = require('../func') +const { + checkBrowserForConsoleErrors, + loadExtension, + verboseReportOnFailure, +} = require('./helpers') + +describe('Using MetaMask with an existing account', function () { + let extensionId + let driver + let tokenAddress + + const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent' + const testAddress = '0xE18035BF8712672935FDB4e5e431b1a0183d2DFC' + const regularDelayMs = 1000 + const largeDelayMs = regularDelayMs * 2 + const waitingNewPageDelayMs = regularDelayMs * 10 + + this.timeout(0) + this.bail(true) + + before(async function () { + switch (process.env.SELENIUM_BROWSER) { + case 'chrome': { + const extensionPath = path.resolve('dist/chrome') + driver = buildChromeWebDriver(extensionPath) + extensionId = await getExtensionIdChrome(driver) + await driver.get(`chrome-extension://${extensionId}/popup.html`) + await delay(regularDelayMs) + break + } + case 'firefox': { + const extensionPath = path.resolve('dist/firefox') + driver = buildFirefoxWebdriver() + await installWebExt(driver, extensionPath) + await delay(regularDelayMs) + extensionId = await getExtensionIdFirefox(driver) + await driver.get(`moz-extension://${extensionId}/popup.html`) + await delay(regularDelayMs) + break + } + } + }) + + afterEach(async function () { + if (process.env.SELENIUM_BROWSER === 'chrome') { + const errors = await checkBrowserForConsoleErrors(driver) + if (errors.length) { + const errorReports = errors.map(err => err.message) + const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` + console.error(new Error(errorMessage)) + } + } + if (this.currentTest.state === 'failed') { + await verboseReportOnFailure(driver, this.currentTest) + } + }) + + after(async function () { + await driver.quit() + }) + + describe('New UI setup', async function () { + it('switches to first tab', async function () { + const [firstTab] = await driver.getAllWindowHandles() + await driver.switchTo().window(firstTab) + await delay(regularDelayMs) + }) + + it('use the local network', async function () { + const [networkSelector] = await driver.findElements(By.css('#network_component')) + await networkSelector.click() + await delay(regularDelayMs) + + const [localhost] = await driver.findElements(By.xpath(`//li[contains(text(), 'Localhost')]`)) + await localhost.click() + await delay(regularDelayMs) + }) + + it('selects the new UI option', async () => { + const button = await driver.findElement(By.xpath("//p[contains(text(), 'Try Beta Version')]")) + await button.click() + await delay(regularDelayMs) + + // Close all other tabs + const [oldUi, infoPage, newUi] = await driver.getAllWindowHandles() + await driver.switchTo().window(oldUi) + await driver.close() + await driver.switchTo().window(infoPage) + await driver.close() + await driver.switchTo().window(newUi) + await delay(regularDelayMs) + + const [continueBtn] = await driver.findElements(By.css('.welcome-screen__button')) + await continueBtn.click() + await delay(regularDelayMs) + }) + }) + + describe('First time flow starting from an existing seed phrase', () => { + it('imports a seed phrase', async () => { + const [seedPhrase] = await driver.findElements(By.xpath(`//a[contains(text(), 'Import with seed phrase')]`)) + await seedPhrase.click() + await delay(regularDelayMs) + + const [seedTextArea] = await driver.findElements(By.css('textarea.import-account__secret-phrase')) + await seedTextArea.sendKeys(testSeedPhrase) + await delay(regularDelayMs) + + const [password] = await driver.findElements(By.id('password')) + await password.sendKeys('correct horse battery staple') + const [confirmPassword] = await driver.findElements(By.id('confirm-password')) + confirmPassword.sendKeys('correct horse battery staple') + + const [importButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Import')]`)) + await importButton.click() + await delay(regularDelayMs) + }) + + it('clicks through the privacy notice', async () => { + const [nextScreen] = await driver.findElements(By.css('.tou button')) + await nextScreen.click() + await delay(regularDelayMs) + + const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled() + assert.equal(canClickThrough, false, 'disabled continue button') + const element = await driver.findElement(By.linkText('Attributions')) + await driver.executeScript('arguments[0].scrollIntoView(true)', element) + await delay(regularDelayMs) + + const [acceptTos] = await driver.findElements(By.css('.tou button')) + await acceptTos.click() + await delay(regularDelayMs) + }) + }) + + describe('Show account information', () => { + it('shows the correct account address', async () => { + await driver.findElement(By.css('.wallet-view__details-button')).click() + await driver.findElement(By.css('.qr-wrapper')).isDisplayed() + await delay(regularDelayMs) + + const [address] = await driver.findElements(By.css('input.qr-ellip-address')) + assert.equal(await address.getAttribute('value'), testAddress) + + await driver.executeScript("document.querySelector('.account-modal-close').click()") + await delay(largeDelayMs) + }) + + it('shows a QR code for the account', async () => { + await driver.findElement(By.css('.wallet-view__details-button')).click() + await driver.findElement(By.css('.qr-wrapper')).isDisplayed() + await delay(regularDelayMs) + + await driver.executeScript("document.querySelector('.account-modal-close').click()") + await delay(regularDelayMs) + }) + }) + + describe('Log out and log back in', () => { + it('logs out of the account', async () => { + await driver.findElement(By.css('.account-menu__icon')).click() + await delay(regularDelayMs) + + const [logoutButton] = await driver.findElements(By.css('.account-menu__logout-button')) + assert.equal(await logoutButton.getText(), 'Log out') + await logoutButton.click() + await delay(regularDelayMs) + }) + + it('accepts the account password after lock', async () => { + await driver.findElement(By.id('password')).sendKeys('correct horse battery staple') + await driver.findElement(By.id('password')).sendKeys(Key.ENTER) + await delay(largeDelayMs) + }) + }) + + describe('Add an account', () => { + it('choose Create Account from the account menu', async () => { + await driver.findElement(By.css('.account-menu__icon')).click() + await delay(regularDelayMs) + + const [createAccount] = await driver.findElements(By.xpath(`//div[contains(text(), 'Create Account')]`)) + await createAccount.click() + await delay(regularDelayMs) + }) + + it('set account name', async () => { + const [accountName] = await driver.findElements(By.css('.new-account-create-form input')) + await accountName.sendKeys('2nd account') + await delay(regularDelayMs) + + const [createButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Create')]`)) + await createButton.click() + await delay(regularDelayMs) + }) + + it('should show the correct account name', async () => { + const [accountName] = await driver.findElements(By.css('.account-name')) + assert.equal(await accountName.getText(), '2nd account') + await delay(regularDelayMs) + }) + }) + + describe('Switch back to original account', () => { + it('chooses the original account from the account menu', async () => { + await driver.findElement(By.css('.account-menu__icon')).click() + await delay(regularDelayMs) + + const [originalAccountMenuItem] = await driver.findElements(By.css('.account-menu__name')) + await originalAccountMenuItem.click() + await delay(regularDelayMs) + }) + }) + + describe('Send ETH from inside MetaMask', () => { + it('starts to send a transaction', async function () { + const [sendButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Send')]`)) + await sendButton.click() + await delay(regularDelayMs) + + const [inputAddress] = await driver.findElements(By.css('input[placeholder="Recipient Address"]')) + const [inputAmount] = await driver.findElements(By.css('.currency-display__input')) + await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') + await inputAmount.sendKeys('1') + + // Set the gas limit + const [configureGas] = await driver.findElements(By.css('.send-v2__gas-fee-display button')) + await configureGas.click() + await delay(regularDelayMs) + + const [save] = await driver.findElements(By.xpath(`//button[contains(text(), 'Save')]`)) + await save.click() + await delay(regularDelayMs) + + // Continue to next screen + const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`)) + await nextScreen.click() + await delay(regularDelayMs) + }) + + it('confirms the transaction', async function () { + const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await confirmButton.click() + await delay(regularDelayMs) + }) + + it('finds the transaction in the transactions list', async function () { + const transactions = await driver.findElements(By.css('.tx-list-item')) + assert.equal(transactions.length, 1) + + const txValues = await driver.findElements(By.css('.tx-list-value')) + assert.equal(txValues.length, 1) + assert.equal(await txValues[0].getText(), '1 ETH') + }) + }) + + describe('Send ETH from Faucet', () => { + it('starts a send transaction inside Faucet', async () => { + await driver.executeScript('window.open("https://faucet.metamask.io")') + await delay(waitingNewPageDelayMs) + + const [extension, faucet] = await driver.getAllWindowHandles() + await driver.switchTo().window(faucet) + await delay(regularDelayMs) + + const [send1eth] = await driver.findElements(By.xpath(`//button[contains(text(), '10 ether')]`)) + await send1eth.click() + await delay(regularDelayMs) + + await driver.switchTo().window(extension) + await loadExtension(driver, extensionId) + await delay(regularDelayMs) + + const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(),'Confirm')]`)) + await confirmButton.click() + await delay(regularDelayMs) + + await driver.switchTo().window(faucet) + await delay(regularDelayMs) + await driver.close() + await delay(regularDelayMs) + await driver.switchTo().window(extension) + await delay(regularDelayMs) + await loadExtension(driver, extensionId) + await delay(regularDelayMs) + }) + }) + + describe('Add existing token using search', () => { + it('clicks on the Add Token button', async () => { + const [addToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Token')]`)) + await addToken.click() + await delay(regularDelayMs) + }) + + it('picks an existing token', async () => { + const [tokenSearch] = await driver.findElements(By.css('input.add-token__input')) + await tokenSearch.sendKeys('BAT') + await delay(regularDelayMs) + + const [token] = await driver.findElements(By.xpath("//div[contains(text(), 'BAT')]")) + await token.click() + await delay(regularDelayMs) + + const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`)) + await nextScreen.click() + await delay(regularDelayMs) + + const [addTokens] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Tokens')]`)) + await addTokens.click() + await delay(largeDelayMs) + }) + + it('renders the balance for the new token', async () => { + const balance = await driver.findElement(By.css('.tx-view .balance-display .token-amount')) + const tokenAmount = await balance.getText() + assert.equal(tokenAmount, '0BAT') + await delay(regularDelayMs) + }) + }) + + describe('Add a custom token from TokenFactory', () => { + it('creates a new token', async () => { + await driver.executeScript('window.open("https://tokenfactory.surge.sh/#/factory")') + await delay(waitingNewPageDelayMs) + + const [extension, tokenFactory] = await driver.getAllWindowHandles() + await driver.switchTo().window(tokenFactory) + const [ + totalSupply, + tokenName, + tokenDecimal, + tokenSymbol, + ] = await driver.findElements(By.css('input')) + + await totalSupply.sendKeys('100') + await tokenName.sendKeys('Test') + await tokenDecimal.sendKeys('0') + await tokenSymbol.sendKeys('TST') + + const [createToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Create Token')]`)) + await createToken.click() + await delay(regularDelayMs) + + await driver.switchTo().window(extension) + await loadExtension(driver, extensionId) + await delay(regularDelayMs) + + const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(),'Confirm')]`)) + await confirmButton.click() + await delay(regularDelayMs) + + await driver.switchTo().window(tokenFactory) + await delay(regularDelayMs) + const tokenContactAddress = await driver.findElement(By.css('div > div > div:nth-child(2) > span:nth-child(3)')) + tokenAddress = await tokenContactAddress.getText() + await driver.close() + await driver.switchTo().window(extension) + await loadExtension(driver, extensionId) + await delay(regularDelayMs) + }) + + it('clicks on the Add Token button', async () => { + const [addToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Token')]`)) + await addToken.click() + await delay(regularDelayMs) + }) + + it('picks the new Test token', async () => { + const [addCustomToken] = await driver.findElements(By.xpath("//div[contains(text(), 'Custom Token')]")) + await addCustomToken.click() + await delay(regularDelayMs) + + const [newTokenAddress] = await driver.findElements(By.css('.add-token__add-custom-form input')) + await newTokenAddress.sendKeys(tokenAddress) + await delay(regularDelayMs) + + const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`)) + await nextScreen.click() + await delay(regularDelayMs) + + const [addTokens] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Tokens')]`)) + await addTokens.click() + await delay(regularDelayMs) + }) + + it('renders the balance for the new token', async () => { + const [balance] = await driver.findElements(By.css('.tx-view .balance-display .token-amount')) + const tokenAmount = await balance.getText() + assert.equal(tokenAmount, '100TST') + await delay(regularDelayMs) + }) + }) +}) diff --git a/test/e2e/beta/helpers.js b/test/e2e/beta/helpers.js new file mode 100644 index 000000000..8307fdc50 --- /dev/null +++ b/test/e2e/beta/helpers.js @@ -0,0 +1,55 @@ +const fs = require('fs') +const mkdirp = require('mkdirp') +const pify = require('pify') + +module.exports = { + checkBrowserForConsoleErrors, + loadExtension, + verboseReportOnFailure, +} + +async function loadExtension (driver, extensionId) { + switch (process.env.SELENIUM_BROWSER) { + case 'chrome': { + await driver.get(`chrome-extension://${extensionId}/home.html`) + break + } + case 'firefox': { + await driver.get(`moz-extension://${extensionId}/home.html`) + break + } + } +} + +async function checkBrowserForConsoleErrors (driver) { + const ignoredLogTypes = ['WARNING'] + const ignoredErrorMessages = [ + // React throws error warnings on "dataset", but still sets the data-* properties correctly + 'Warning: Unknown prop `dataset` on ', + // Third-party Favicon 404s show up as errors + 'favicon.ico - Failed to load resource: the server responded with a status of 404 (Not Found)', + // React Development build - known issue blocked by test build sys + 'Warning: It looks like you\'re using a minified copy of the development build of React.', + // Redux Development build - known issue blocked by test build sys + 'This means that you are running a slower development build of Redux.', + ] + const browserLogs = await driver.manage().logs().get('browser') + const errorEntries = browserLogs.filter(entry => !ignoredLogTypes.includes(entry.level.toString())) + const errorObjects = errorEntries.map(entry => entry.toJSON()) + return errorObjects.filter(entry => !ignoredErrorMessages.some(message => entry.message.includes(message))) +} + +async function verboseReportOnFailure (driver, test) { + let artifactDir + if (process.env.SELENIUM_BROWSER === 'chrome') { + artifactDir = `./test-artifacts/chrome/${test.title}` + } else if (process.env.SELENIUM_BROWSER === 'firefox') { + artifactDir = `./test-artifacts/firefox/${test.title}` + } + const filepathBase = `${artifactDir}/test-failure` + await pify(mkdirp)(artifactDir) + const screenshot = await driver.takeScreenshot() + await pify(fs.writeFile)(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' }) + const htmlSource = await driver.getPageSource() + await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource) +} diff --git a/test/e2e/beta/metamask-beta-ui.spec.js b/test/e2e/beta/metamask-beta-ui.spec.js new file mode 100644 index 000000000..00863e3b3 --- /dev/null +++ b/test/e2e/beta/metamask-beta-ui.spec.js @@ -0,0 +1,491 @@ +const path = require('path') +const assert = require('assert') +const webdriver = require('selenium-webdriver') +const { By, Key } = webdriver +const { + delay, + buildChromeWebDriver, + buildFirefoxWebdriver, + installWebExt, + getExtensionIdChrome, + getExtensionIdFirefox, +} = require('../func') +const { + checkBrowserForConsoleErrors, + loadExtension, + verboseReportOnFailure, +} = require('./helpers') + +describe('MetaMask', function () { + let extensionId + let driver + let tokenAddress + + const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent' + const tinyDelayMs = 500 + const regularDelayMs = tinyDelayMs * 2 + const largeDelayMs = regularDelayMs * 2 + const waitingNewPageDelayMs = regularDelayMs * 10 + + this.timeout(0) + this.bail(true) + + before(async function () { + switch (process.env.SELENIUM_BROWSER) { + case 'chrome': { + const extPath = path.resolve('dist/chrome') + driver = buildChromeWebDriver(extPath) + extensionId = await getExtensionIdChrome(driver) + await driver.get(`chrome-extension://${extensionId}/popup.html`) + break + } + case 'firefox': { + const extPath = path.resolve('dist/firefox') + driver = buildFirefoxWebdriver() + await installWebExt(driver, extPath) + await delay(700) + extensionId = await getExtensionIdFirefox(driver) + await driver.get(`moz-extension://${extensionId}/popup.html`) + } + } + }) + + afterEach(async function () { + if (process.env.SELENIUM_BROWSER === 'chrome') { + const errors = await checkBrowserForConsoleErrors(driver) + if (errors.length) { + const errorReports = errors.map(err => err.message) + const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` + console.error(new Error(errorMessage)) + } + } + if (this.currentTest.state === 'failed') { + await verboseReportOnFailure(this.currentTest) + } + }) + + after(async function () { + await driver.quit() + }) + + describe('New UI setup', async function () { + it('switches to first tab', async function () { + const [firstTab] = await driver.getAllWindowHandles() + await driver.switchTo().window(firstTab) + await delay(regularDelayMs) + }) + + it('use the local network', async function () { + const [networkSelector] = await driver.findElements(By.css('#network_component')) + await networkSelector.click() + await delay(regularDelayMs) + + const [localhost] = await driver.findElements(By.xpath(`//li[contains(text(), 'Localhost')]`)) + await localhost.click() + await delay(regularDelayMs) + }) + + it('selects the new UI option', async () => { + const button = await driver.findElement(By.xpath("//p[contains(text(), 'Try Beta Version')]")) + await button.click() + await delay(regularDelayMs) + + // Close all other tabs + const [oldUi, infoPage, newUi] = await driver.getAllWindowHandles() + await driver.switchTo().window(oldUi) + await driver.close() + await driver.switchTo().window(infoPage) + await driver.close() + await driver.switchTo().window(newUi) + await delay(regularDelayMs) + + const [continueBtn] = await driver.findElements(By.css('.welcome-screen__button')) + await continueBtn.click() + await delay(regularDelayMs) + }) + }) + + describe('Going through the first time flow', () => { + it('accepts a secure password', async () => { + const [passwordBox] = await driver.findElements(By.css('.create-password #create-password')) + const [passwordBoxConfirm] = await driver.findElements(By.css('.create-password #confirm-password')) + const [button] = await driver.findElements(By.css('.create-password button')) + + await passwordBox.sendKeys('correct horse battery staple') + await passwordBoxConfirm.sendKeys('correct horse battery staple') + await button.click() + await delay(regularDelayMs) + }) + + it('clicks through the unique image screen', async () => { + const [nextScreen] = await driver.findElements(By.css('.unique-image button')) + await nextScreen.click() + await delay(regularDelayMs) + }) + + it('clicks through the privacy notice', async () => { + const [nextScreen] = await driver.findElements(By.css('.tou button')) + await nextScreen.click() + await delay(regularDelayMs) + + const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled() + assert.equal(canClickThrough, false, 'disabled continue button') + const [bottomOfTos] = await driver.findElements(By.linkText('Attributions')) + await driver.executeScript('arguments[0].scrollIntoView(true)', bottomOfTos) + await delay(regularDelayMs) + + const [acceptTos] = await driver.findElements(By.css('.tou button')) + await acceptTos.click() + await delay(regularDelayMs) + }) + + let seedPhrase + + it('reveals the seed phrase', async () => { + const [revealSeedPhrase] = await driver.findElements(By.css('.backup-phrase__secret-blocker')) + await revealSeedPhrase.click() + await delay(regularDelayMs) + + seedPhrase = await driver.findElement(By.css('.backup-phrase__secret-words')).getText() + assert.equal(seedPhrase.split(' ').length, 12) + await delay(regularDelayMs) + + const [nextScreen] = await driver.findElements(By.css('.backup-phrase button')) + await nextScreen.click() + await delay(regularDelayMs) + }) + + it('can retype the seed phrase', async () => { + const words = seedPhrase.split(' ') + + const [word0] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[0]}')]`)) + await word0.click() + await delay(tinyDelayMs) + + const [word1] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[1]}')]`)) + await word1.click() + await delay(tinyDelayMs) + + const [word2] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[2]}')]`)) + await word2.click() + await delay(tinyDelayMs) + + const [word3] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[3]}')]`)) + await word3.click() + await delay(tinyDelayMs) + + const [word4] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[4]}')]`)) + await word4.click() + await delay(tinyDelayMs) + + const [word5] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[5]}')]`)) + await word5.click() + await delay(tinyDelayMs) + + const [word6] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[6]}')]`)) + await word6.click() + await delay(tinyDelayMs) + + const [word7] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[7]}')]`)) + await word7.click() + await delay(tinyDelayMs) + + const [word8] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[8]}')]`)) + await word8.click() + await delay(tinyDelayMs) + + const [word9] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[9]}')]`)) + await word9.click() + await delay(tinyDelayMs) + + const [word10] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[10]}')]`)) + await word10.click() + await delay(tinyDelayMs) + + const [word11] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[11]}')]`)) + await word11.click() + await delay(tinyDelayMs) + + const [confirm] = await driver.findElements(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await confirm.click() + await delay(regularDelayMs) + }) + + it('clicks through the deposit modal', async () => { + const [closeModal] = await driver.findElements(By.css('.page-container__header-close')) + await closeModal.click() + await delay(regularDelayMs) + }) + }) + + describe('Show account information', () => { + it('shows the QR code for the account', async () => { + await driver.findElement(By.css('.wallet-view__details-button')).click() + await driver.findElement(By.css('.qr-wrapper')).isDisplayed() + await delay(regularDelayMs) + + await driver.executeScript("document.querySelector('.account-modal-close').click()") + await delay(regularDelayMs * 4) + }) + }) + + describe('Log out an log back in', () => { + it('logs out of the account', async () => { + await driver.findElement(By.css('.account-menu__icon')).click() + await delay(regularDelayMs) + + const [logoutButton] = await driver.findElements(By.css('.account-menu__logout-button')) + assert.equal(await logoutButton.getText(), 'Log out') + await logoutButton.click() + await delay(regularDelayMs) + }) + + it('accepts the account password after lock', async () => { + await driver.findElement(By.id('password')).sendKeys('correct horse battery staple') + await driver.findElement(By.id('password')).sendKeys(Key.ENTER) + await delay(regularDelayMs * 4) + }) + }) + + describe('Add account', () => { + it('choose Create Account from the account menu', async () => { + await driver.findElement(By.css('.account-menu__icon')).click() + await delay(regularDelayMs) + + const [createAccount] = await driver.findElements(By.xpath(`//div[contains(text(), 'Create Account')]`)) + await createAccount.click() + await delay(regularDelayMs) + }) + + it('set account name', async () => { + const [accountName] = await driver.findElements(By.css('.new-account-create-form input')) + await accountName.sendKeys('2nd account') + await delay(regularDelayMs) + + const [create] = await driver.findElements(By.xpath(`//button[contains(text(), 'Create')]`)) + await create.click() + await delay(regularDelayMs) + }) + + it('should correct account name', async () => { + const [accountName] = await driver.findElements(By.css('.account-name')) + assert.equal(await accountName.getText(), '2nd account') + await delay(regularDelayMs) + }) + }) + + describe('Import seed phrase', () => { + it('logs out of the vault', async () => { + await driver.findElement(By.css('.account-menu__icon')).click() + await delay(regularDelayMs) + + const [logoutButton] = await driver.findElements(By.css('.account-menu__logout-button')) + assert.equal(await logoutButton.getText(), 'Log out') + await logoutButton.click() + await delay(regularDelayMs) + }) + + it('imports seed phrase', async () => { + const [restoreSeedLink] = await driver.findElements(By.css('.unlock-page__link--import')) + assert.equal(await restoreSeedLink.getText(), 'Import using account seed phrase') + await restoreSeedLink.click() + await delay(regularDelayMs) + + const [seedTextArea] = await driver.findElements(By.css('textarea')) + await seedTextArea.sendKeys(testSeedPhrase) + await delay(regularDelayMs) + + await driver.findElement(By.id('password-box')).sendKeys('correct horse battery staple') + await driver.findElement(By.id('password-box-confirm')).sendKeys('correct horse battery staple') + await driver.findElement(By.css('button:nth-child(2)')).click() + await delay(regularDelayMs) + }) + + it('balance renders', async () => { + const balance = await driver.findElement(By.css('.balance-display .token-amount')) + const tokenAmount = await balance.getText() + assert.equal(tokenAmount, '100.000 ETH') + await delay(regularDelayMs) + }) + }) + + describe('Send ETH from inside MetaMask', () => { + it('starts to send a transaction', async function () { + const [sendButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Send')]`)) + await sendButton.click() + await delay(regularDelayMs) + + const [inputAddress] = await driver.findElements(By.css('input[placeholder="Recipient Address"]')) + const [inputAmount] = await driver.findElements(By.css('.currency-display__input')) + await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') + await inputAmount.sendKeys('1') + + // Set the gas limit + const [configureGas] = await driver.findElements(By.css('.send-v2__gas-fee-display button')) + await configureGas.click() + await delay(regularDelayMs) + + const [save] = await driver.findElements(By.xpath(`//button[contains(text(), 'Save')]`)) + await save.click() + await delay(regularDelayMs) + + // Continue to next screen + const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`)) + await nextScreen.click() + await delay(regularDelayMs) + }) + + it('confirms the transaction', async function () { + const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await confirmButton.click() + await delay(regularDelayMs) + }) + + it('finds the transaction in the transactions list', async function () { + const transactions = await driver.findElements(By.css('.tx-list-item')) + assert.equal(transactions.length, 1) + + const txValues = await driver.findElements(By.css('.tx-list-value')) + assert.equal(txValues.length, 1) + assert.equal(await txValues[0].getText(), '1 ETH') + }) + }) + + describe('Send ETH from Faucet', () => { + it('starts a send transaction inside Faucet', async () => { + await driver.executeScript('window.open("https://faucet.metamask.io")') + await delay(waitingNewPageDelayMs) + + const [extension, faucet] = await driver.getAllWindowHandles() + await driver.switchTo().window(faucet) + await delay(regularDelayMs) + + const [send1eth] = await driver.findElements(By.xpath(`//button[contains(text(), '10 ether')]`)) + await send1eth.click() + await delay(regularDelayMs) + + await driver.switchTo().window(extension) + await loadExtension(driver, extensionId) + await delay(regularDelayMs) + + const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await confirmButton.click() + await delay(regularDelayMs) + + await driver.switchTo().window(faucet) + await delay(regularDelayMs) + await driver.close() + await delay(regularDelayMs) + await driver.switchTo().window(extension) + await delay(regularDelayMs) + await loadExtension(driver, extensionId) + await delay(regularDelayMs) + }) + }) + + describe('Add existing token using search', () => { + it('clicks on the Add Token button', async () => { + const [addToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Token')]`)) + await addToken.click() + await delay(regularDelayMs) + }) + + it('can pick a token from the existing options', async () => { + const [tokenSearch] = await driver.findElements(By.css('input.add-token__input')) + await tokenSearch.sendKeys('BAT') + await delay(regularDelayMs) + + const [token] = await driver.findElements(By.xpath("//div[contains(text(), 'BAT')]")) + await token.click() + await delay(regularDelayMs) + + const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`)) + await nextScreen.click() + await delay(regularDelayMs) + + const [addTokens] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Tokens')]`)) + await addTokens.click() + await delay(largeDelayMs) + }) + + it('renders the balance for the chosen token', async () => { + const balance = await driver.findElement(By.css('.tx-view .balance-display .token-amount')) + const tokenAmount = await balance.getText() + assert.equal(tokenAmount, '0BAT') + await delay(regularDelayMs) + }) + }) + + describe('Add a custom token from TokenFactory', () => { + it('creates a new token', async () => { + await driver.executeScript('window.open("https://tokenfactory.surge.sh/#/factory")') + await delay(waitingNewPageDelayMs) + + const [extension, tokenFactory] = await driver.getAllWindowHandles() + await driver.switchTo().window(tokenFactory) + const [ + totalSupply, + tokenName, + tokenDecimal, + tokenSymbol, + ] = await driver.findElements(By.css('input')) + + await totalSupply.sendKeys('100') + await tokenName.sendKeys('Test') + await tokenDecimal.sendKeys('0') + await tokenSymbol.sendKeys('TST') + + const [createToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Create Token')]`)) + await createToken.click() + await delay(regularDelayMs) + + await driver.switchTo().window(extension) + await loadExtension(driver, extensionId) + await delay(regularDelayMs) + + const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await confirmButton.click() + await delay(regularDelayMs) + + await driver.switchTo().window(tokenFactory) + await delay(regularDelayMs) + const tokenContactAddress = await driver.findElement(By.css('div > div > div:nth-child(2) > span:nth-child(3)')) + tokenAddress = await tokenContactAddress.getText() + await driver.close() + await driver.switchTo().window(extension) + await loadExtension(driver, extensionId) + await delay(regularDelayMs) + }) + + it('clicks on the Add Token button', async () => { + const [addToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Token')]`)) + await addToken.click() + await delay(regularDelayMs) + }) + + it('picks the newly created Test token', async () => { + const [addCustomToken] = await driver.findElements(By.xpath("//div[contains(text(), 'Custom Token')]")) + await addCustomToken.click() + await delay(regularDelayMs) + + const [newTokenAddress] = await driver.findElements(By.css('.add-token__add-custom-form input')) + await newTokenAddress.sendKeys(tokenAddress) + await delay(regularDelayMs) + + const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`)) + await nextScreen.click() + await delay(regularDelayMs) + + const [addTokens] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Tokens')]`)) + await addTokens.click() + await delay(regularDelayMs) + }) + + it('renders the balance for the new token', async () => { + const [balance] = await driver.findElements(By.css('.tx-view .balance-display .token-amount')) + const tokenAmount = await balance.getText() + assert.equal(tokenAmount, '100TST') + await delay(regularDelayMs) + }) + }) +}) diff --git a/test/e2e/beta/run-all.sh b/test/e2e/beta/run-all.sh new file mode 100755 index 000000000..5916d5614 --- /dev/null +++ b/test/e2e/beta/run-all.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +export PATH="$PATH:./node_modules/.bin" + +shell-parallel -s 'npm run ganache:start' -x 'sleep 5 && mocha test/e2e/beta/metamask-beta-ui.spec' +shell-parallel -s 'npm run ganache:start' -x 'sleep 5 && mocha test/e2e/beta/from-import-beta-ui.spec' diff --git a/test/e2e/func.js b/test/e2e/func.js index 8b221ce47..9f06e7f37 100644 --- a/test/e2e/func.js +++ b/test/e2e/func.js @@ -1,5 +1,7 @@ require('chromedriver') require('geckodriver') +const fs = require('fs') +const os = require('os') const path = require('path') const webdriver = require('selenium-webdriver') const Command = require('selenium-webdriver/lib/command').Command @@ -19,10 +21,15 @@ function delay (time) { } function buildChromeWebDriver (extPath) { + const tmpProfile = path.join(os.tmpdir(), fs.mkdtempSync('mm-chrome-profile')); return new webdriver.Builder() .withCapabilities({ chromeOptions: { - args: [`load-extension=${extPath}`], + args: [ + `load-extension=${extPath}`, + `user-data-dir=${tmpProfile}`, + ], + binary: process.env.SELENIUM_CHROME_BINARY, }, }) .build() @@ -53,4 +60,4 @@ async function installWebExt (driver, extension) { .defineCommand(cmd.getName(), 'POST', '/session/:sessionId/moz/addon/install') return await driver.schedule(cmd, 'installWebExt(' + extension + ')') -} \ No newline at end of file +} From 1cd4b03b3759691f03457eadc8860dcea9570ae9 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 25 May 2018 12:58:16 -0700 Subject: [PATCH 08/31] test - unit - fail if tests contain a .only call --- package-lock.json | 6 ++++++ package.json | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index a1fdf0059..e138ec3ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7134,6 +7134,12 @@ "domelementtype": "1.3.0" } }, + "dot-only-hunter": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dot-only-hunter/-/dot-only-hunter-1.0.3.tgz", + "integrity": "sha1-9k0h7b5v8xFJlfEGGmGpNcMAIEs=", + "dev": true + }, "dotenv": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-5.0.1.tgz", diff --git a/package.json b/package.json index 372ffbfa5..5ad1f9639 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "dist": "gulp dist", "doc": "jsdoc -c development/tools/.jsdoc.json", "test": "npm run test:unit && npm run test:integration && npm run lint", - "test:unit": "cross-env METAMASK_ENV=test mocha --exit --require test/setup.js --recursive \"test/unit/**/*.js\"", + "test:unit": "cross-env METAMASK_ENV=test mocha --exit --require test/setup.js --recursive \"test/unit/**/*.js\" && dot-only-hunter", "test:single": "cross-env METAMASK_ENV=test mocha --require test/helper.js", "test:integration": "npm run test:integration:build && npm run test:flat && npm run test:mascara", "test:integration:build": "gulp build:scss", @@ -219,6 +219,7 @@ "css-loader": "^0.28.11", "deep-freeze-strict": "^1.1.1", "del": "^3.0.0", + "dot-only-hunter": "^1.0.3", "envify": "^4.0.0", "enzyme": "^3.3.0", "enzyme-adapter-react-15": "^1.0.5", From 62dc6e20eb1f7188c6452519782e8ebe54c1c540 Mon Sep 17 00:00:00 2001 From: Anton Date: Mon, 28 May 2018 17:57:45 +0200 Subject: [PATCH 09/31] Clean up user rejection error message --- app/scripts/controllers/transactions/index.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 541f1db73..586a80baf 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -111,6 +111,15 @@ class TransactionController extends EventEmitter { this.txStateManager.wipeTransactions(address) } + /** + Returns error without stack trace for better UI display + @param {Error} err - error which stack will be cleaned + */ + cleanErrorStack(err){ + err.stack = err.name + ': ' + err.message + return err + } + /** add a new unapproved transaction to the pipeline @@ -118,6 +127,8 @@ class TransactionController extends EventEmitter { @param txParams {object} - txParams for the transaction @param opts {object} - with the key origin to put the origin on the txMeta */ + + async newUnapprovedTransaction (txParams, opts = {}) { log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`) const initialTxMeta = await this.addUnapprovedTransaction(txParams) @@ -130,11 +141,11 @@ class TransactionController extends EventEmitter { case 'submitted': return resolve(finishedTxMeta.hash) case 'rejected': - return reject(new Error('MetaMask Tx Signature: User denied transaction signature.')) + return reject(this.cleanErrorStack(new Error('MetaMask Tx Signature: User denied transaction signature.'))) case 'failed': - return reject(new Error(finishedTxMeta.err.message)) + return reject(this.cleanErrorStack(new Error(finishedTxMeta.err.message))) default: - return reject(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`)) + return reject(this.cleanErrorStack(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`)) } }) }) From 1d23a5c81b03b8b52225e942603c84b237d4e63c Mon Sep 17 00:00:00 2001 From: Anton Date: Mon, 28 May 2018 18:08:33 +0200 Subject: [PATCH 10/31] error message fix --- app/scripts/controllers/transactions/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 586a80baf..6609b80b0 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -128,7 +128,6 @@ class TransactionController extends EventEmitter { @param opts {object} - with the key origin to put the origin on the txMeta */ - async newUnapprovedTransaction (txParams, opts = {}) { log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`) const initialTxMeta = await this.addUnapprovedTransaction(txParams) @@ -145,7 +144,7 @@ class TransactionController extends EventEmitter { case 'failed': return reject(this.cleanErrorStack(new Error(finishedTxMeta.err.message))) default: - return reject(this.cleanErrorStack(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`)) + return reject(this.cleanErrorStack(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`))) } }) }) From 71a6e97327a4c759942784ee81505e3bc5ed545e Mon Sep 17 00:00:00 2001 From: Anton Date: Mon, 28 May 2018 22:57:08 +0200 Subject: [PATCH 11/31] cleanErrorStack moved to separate library module more errors traces cleaned up --- app/scripts/controllers/transactions/index.js | 16 ++++--------- app/scripts/lib/cleanErrorStack.js | 24 +++++++++++++++++++ app/scripts/metamask-controller.js | 15 ++++++------ 3 files changed, 36 insertions(+), 19 deletions(-) create mode 100644 app/scripts/lib/cleanErrorStack.js diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 6609b80b0..aff5db984 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -8,6 +8,7 @@ const TxGasUtil = require('./tx-gas-utils') const PendingTransactionTracker = require('./pending-tx-tracker') const NonceTracker = require('./nonce-tracker') const txUtils = require('./lib/util') +const cleanErrorStack = require('../../lib/cleanErrorStack') const log = require('loglevel') /** @@ -111,15 +112,6 @@ class TransactionController extends EventEmitter { this.txStateManager.wipeTransactions(address) } - /** - Returns error without stack trace for better UI display - @param {Error} err - error which stack will be cleaned - */ - cleanErrorStack(err){ - err.stack = err.name + ': ' + err.message - return err - } - /** add a new unapproved transaction to the pipeline @@ -140,11 +132,11 @@ class TransactionController extends EventEmitter { case 'submitted': return resolve(finishedTxMeta.hash) case 'rejected': - return reject(this.cleanErrorStack(new Error('MetaMask Tx Signature: User denied transaction signature.'))) + return reject(cleanErrorStack(new Error('MetaMask Tx Signature: User denied transaction signature.'))) case 'failed': - return reject(this.cleanErrorStack(new Error(finishedTxMeta.err.message))) + return reject(cleanErrorStack(new Error(finishedTxMeta.err.message))) default: - return reject(this.cleanErrorStack(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`))) + return reject(cleanErrorStack(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`))) } }) }) diff --git a/app/scripts/lib/cleanErrorStack.js b/app/scripts/lib/cleanErrorStack.js new file mode 100644 index 000000000..fe1bfb0ce --- /dev/null +++ b/app/scripts/lib/cleanErrorStack.js @@ -0,0 +1,24 @@ +/** + * Returns error without stack trace for better UI display + * @param {Error} err - error + * @returns {Error} Error with clean stack trace. + */ +function cleanErrorStack(err){ + var name = err.name + name = (name === undefined) ? 'Error' : String(name) + + var msg = err.message + msg = (msg === undefined) ? '' : String(msg) + + if (name === '') { + err.stack = err.message + } else if (msg === '') { + err.stack = err.name + } else { + err.stack = err.name + ': ' + err.message + } + + return err +} + +module.exports = cleanErrorStack diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index c4a73d8ea..b0666d9f9 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -45,6 +45,7 @@ const BN = require('ethereumjs-util').BN const GWEI_BN = new BN('1000000000') const percentile = require('percentile') const seedPhraseVerifier = require('./lib/seed-phrase-verifier') +const cleanErrorStack = require('./lib/cleanErrorStack') const log = require('loglevel') module.exports = class MetamaskController extends EventEmitter { @@ -637,9 +638,9 @@ module.exports = class MetamaskController extends EventEmitter { case 'signed': return cb(null, data.rawSig) case 'rejected': - return cb(new Error('MetaMask Message Signature: User denied message signature.')) + return cb(cleanErrorStack(new Error('MetaMask Message Signature: User denied message signature.'))) default: - return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) + return cb(cleanErrorStack(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))) } }) } @@ -697,7 +698,7 @@ module.exports = class MetamaskController extends EventEmitter { */ newUnsignedPersonalMessage (msgParams, cb) { if (!msgParams.from) { - return cb(new Error('MetaMask Message Signature: from field is required.')) + return cb(cleanErrorStack(new Error('MetaMask Message Signature: from field is required.'))) } const msgId = this.personalMessageManager.addUnapprovedMessage(msgParams) @@ -708,9 +709,9 @@ module.exports = class MetamaskController extends EventEmitter { case 'signed': return cb(null, data.rawSig) case 'rejected': - return cb(new Error('MetaMask Message Signature: User denied message signature.')) + return cb(cleanErrorStack(new Error('MetaMask Message Signature: User denied message signature.'))) default: - return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) + return cb(cleanErrorStack(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))) } }) } @@ -776,9 +777,9 @@ module.exports = class MetamaskController extends EventEmitter { case 'signed': return cb(null, data.rawSig) case 'rejected': - return cb(new Error('MetaMask Message Signature: User denied message signature.')) + return cb(cleanErrorStack(new Error('MetaMask Message Signature: User denied message signature.'))) default: - return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) + return cb(cleanErrorStack(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))) } }) } From e3ecc94a521b040d8937bd7aaed2ecc7f029c586 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 28 May 2018 21:50:23 -0700 Subject: [PATCH 12/31] i18n - getFirstPreferredLangCode - guard against missing i18n api fix for brave --- app/scripts/lib/get-first-preferred-lang-code.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/scripts/lib/get-first-preferred-lang-code.js b/app/scripts/lib/get-first-preferred-lang-code.js index 5473fccf0..1e6a83ba6 100644 --- a/app/scripts/lib/get-first-preferred-lang-code.js +++ b/app/scripts/lib/get-first-preferred-lang-code.js @@ -2,6 +2,12 @@ const extension = require('extensionizer') const promisify = require('pify') const allLocales = require('../../_locales/index.json') +const isSupported = extension.i18n && extension.i18n.getAcceptLanguages +const getPreferredLocales = isSupported ? promisify( + extension.i18n.getAcceptLanguages, + { errorFirst: false } +) : async () => [] + const existingLocaleCodes = allLocales.map(locale => locale.code.toLowerCase().replace('_', '-')) /** @@ -12,10 +18,7 @@ const existingLocaleCodes = allLocales.map(locale => locale.code.toLowerCase().r * */ async function getFirstPreferredLangCode () { - const userPreferredLocaleCodes = await promisify( - extension.i18n.getAcceptLanguages, - { errorFirst: false } - )() + const userPreferredLocaleCodes = await getPreferredLocales() const firstPreferredLangCode = userPreferredLocaleCodes .map(code => code.toLowerCase()) .find(code => existingLocaleCodes.includes(code)) From 09601439e38e2681c434f23bc77c3b7665f8c98a Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 28 May 2018 22:58:14 -0700 Subject: [PATCH 13/31] metamask-controller - update preferences controller addresses after import account --- app/scripts/metamask-controller.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 1b1d26886..01adc3596 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -350,7 +350,7 @@ module.exports = class MetamaskController extends EventEmitter { verifySeedPhrase: nodeify(this.verifySeedPhrase, this), clearSeedWordCache: this.clearSeedWordCache.bind(this), resetAccount: nodeify(this.resetAccount, this), - importAccountWithStrategy: this.importAccountWithStrategy.bind(this), + importAccountWithStrategy: nodeify(this.importAccountWithStrategy, this), // vault management submitPassword: nodeify(keyringController.submitPassword, keyringController), @@ -608,15 +608,15 @@ module.exports = class MetamaskController extends EventEmitter { * @param {any} args - The data required by that strategy to import an account. * @param {Function} cb - A callback function called with a state update on success. */ - importAccountWithStrategy (strategy, args, cb) { - accountImporter.importAccount(strategy, args) - .then((privateKey) => { - return this.keyringController.addNewKeyring('Simple Key Pair', [ privateKey ]) - }) - .then(keyring => keyring.getAccounts()) - .then((accounts) => this.preferencesController.setSelectedAddress(accounts[0])) - .then(() => { cb(null, this.keyringController.fullUpdate()) }) - .catch((reason) => { cb(reason) }) + async importAccountWithStrategy (strategy, args) { + const privateKey = await accountImporter.importAccount(strategy, args) + const keyring = await this.keyringController.addNewKeyring('Simple Key Pair', [ privateKey ]) + const accounts = await keyring.getAccounts() + // update accounts in preferences controller + const allAccounts = await keyringController.getAccounts() + this.preferencesController.setAddresses(allAccounts) + // set new account as selected + await this.preferencesController.setSelectedAddress(accounts[0]) } // --------------------------------------------------------------------------- From 7e87600042805727a9e99e62c8bf23819f6d0b0f Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 28 May 2018 23:14:38 -0700 Subject: [PATCH 14/31] metamask-controller - lint fix --- app/scripts/metamask-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 01adc3596..0457b4476 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -613,7 +613,7 @@ module.exports = class MetamaskController extends EventEmitter { const keyring = await this.keyringController.addNewKeyring('Simple Key Pair', [ privateKey ]) const accounts = await keyring.getAccounts() // update accounts in preferences controller - const allAccounts = await keyringController.getAccounts() + const allAccounts = await this.keyringController.getAccounts() this.preferencesController.setAddresses(allAccounts) // set new account as selected await this.preferencesController.setSelectedAddress(accounts[0]) From a7b7c8f0349ac8db16f264268745fd4b14e89422 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 28 May 2018 23:34:40 -0700 Subject: [PATCH 15/31] newui - unlock - dont catch errors unrelated to tryUnlockMetamask --- ui/app/components/pages/unlock-page/unlock-page.component.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/app/components/pages/unlock-page/unlock-page.component.js b/ui/app/components/pages/unlock-page/unlock-page.component.js index a2f009d8f..b6384b32d 100644 --- a/ui/app/components/pages/unlock-page/unlock-page.component.js +++ b/ui/app/components/pages/unlock-page/unlock-page.component.js @@ -37,8 +37,8 @@ class UnlockPage extends Component { tryUnlockMetamask (password) { const { tryUnlockMetamask, history } = this.props tryUnlockMetamask(password) - .then(() => history.push(DEFAULT_ROUTE)) .catch(({ message }) => this.setState({ error: message })) + .then(() => history.push(DEFAULT_ROUTE)) } handleSubmit (event) { @@ -55,8 +55,8 @@ class UnlockPage extends Component { this.setState({ error: null }) tryUnlockMetamask(password) - .then(() => history.push(DEFAULT_ROUTE)) .catch(({ message }) => this.setState({ error: message })) + .then(() => history.push(DEFAULT_ROUTE)) } handleInputChange ({ target }) { From d1f5d8ccc663bdc379864e155f12f580af127e8c Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Tue, 29 May 2018 09:35:18 -0500 Subject: [PATCH 16/31] Fix text field labels of first time flow. Add text fields to storybook (#4389) --- .../app/first-time/create-password-screen.js | 2 + .../first-time/import-seed-phrase-screen.js | 2 + .../text-field/text-field.component.js | 95 ++++++++++--------- .../text-field/text-field.stories.js | 29 ++++++ 4 files changed, 84 insertions(+), 44 deletions(-) diff --git a/mascara/src/app/first-time/create-password-screen.js b/mascara/src/app/first-time/create-password-screen.js index 99d210ed1..6b284f7c5 100644 --- a/mascara/src/app/first-time/create-password-screen.js +++ b/mascara/src/app/first-time/create-password-screen.js @@ -143,6 +143,7 @@ class CreatePasswordScreen extends Component { autoComplete="new-password" margin="normal" fullWidth + largeLabel /> + + + ) + } +} + +TransactionConfirmed.propTypes = { + hideModal: PropTypes.func.isRequired, + onHide: PropTypes.func.isRequired, +} + +TransactionConfirmed.contextTypes = { + t: PropTypes.func, +} + +export default TransactionConfirmed diff --git a/ui/app/components/modals/transaction-confirmed/transaction-confirmed.container.js b/ui/app/components/modals/transaction-confirmed/transaction-confirmed.container.js new file mode 100644 index 000000000..63872f7f2 --- /dev/null +++ b/ui/app/components/modals/transaction-confirmed/transaction-confirmed.container.js @@ -0,0 +1,20 @@ +import { connect } from 'react-redux' +import TransactionConfirmed from './transaction-confirmed.component' + +const { hideModal } = require('../../../actions') + +const mapStateToProps = state => { + const { appState: { modal: { modalState: { props } } } } = state + const { onHide } = props + return { + onHide, + } +} + +const mapDispatchToProps = dispatch => { + return { + hideModal: () => dispatch(hideModal()), + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(TransactionConfirmed) diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js index c07c96ccc..5ad35c269 100644 --- a/ui/app/components/pending-tx/confirm-send-ether.js +++ b/ui/app/components/pending-tx/confirm-send-ether.js @@ -291,18 +291,48 @@ ConfirmSendEther.prototype.convertToRenderableCurrency = function (value, curren : value } -ConfirmSendEther.prototype.editTransaction = function (txMeta) { +ConfirmSendEther.prototype.editTransaction = function () { const { editTransaction, history } = this.props + const txMeta = this.gatherTxMeta() editTransaction(txMeta) history.push(SEND_ROUTE) } -ConfirmSendEther.prototype.renderNetworkDisplay = function () { +ConfirmSendEther.prototype.renderHeaderRow = function (isTxReprice) { const windowType = window.METAMASK_UI_TYPE + const isFullScreen = windowType !== ENVIRONMENT_TYPE_NOTIFICATION && + windowType !== ENVIRONMENT_TYPE_POPUP - return (windowType === ENVIRONMENT_TYPE_NOTIFICATION || windowType === ENVIRONMENT_TYPE_POPUP) - ? h(NetworkDisplay) - : null + if (isTxReprice && isFullScreen) { + return null + } + + return ( + h('.page-container__header-row', [ + h('span.page-container__back-button', { + onClick: () => this.editTransaction(), + style: { + visibility: isTxReprice ? 'hidden' : 'initial', + }, + }, 'Edit'), + !isFullScreen && h(NetworkDisplay), + ]) + ) +} + +ConfirmSendEther.prototype.renderHeader = function (isTxReprice) { + const title = isTxReprice ? this.context.t('speedUpTitle') : this.context.t('confirm') + const subtitle = isTxReprice + ? this.context.t('speedUpSubtitle') + : this.context.t('pleaseReviewTransaction') + + return ( + h('.page-container__header', [ + this.renderHeaderRow(isTxReprice), + h('.page-container__title', title), + h('.page-container__subtitle', subtitle), + ]) + ) } ConfirmSendEther.prototype.render = function () { @@ -320,6 +350,7 @@ ConfirmSendEther.prototype.render = function () { }, } = this.props const txMeta = this.gatherTxMeta() + const isTxReprice = Boolean(txMeta.lastGasPrice) const txParams = txMeta.txParams || {} const { @@ -338,11 +369,6 @@ ConfirmSendEther.prototype.render = function () { totalInETH, } = this.getData() - const title = txMeta.lastGasPrice ? 'Reprice Transaction' : 'Confirm' - const subtitle = txMeta.lastGasPrice - ? 'Increase your gas fee to attempt to overwrite and speed up your transaction' - : 'Please review your transaction.' - const convertedAmountInFiat = this.convertToRenderableCurrency(amountInFIAT, currentCurrency) const convertedTotalInFiat = this.convertToRenderableCurrency(totalInFIAT, currentCurrency) @@ -362,19 +388,7 @@ ConfirmSendEther.prototype.render = function () { return ( // Main Send token Card h('.page-container', [ - h('.page-container__header', [ - h('.page-container__header-row', [ - h('span.page-container__back-button', { - onClick: () => this.editTransaction(txMeta), - style: { - visibility: !txMeta.lastGasPrice ? 'initial' : 'hidden', - }, - }, 'Edit'), - this.renderNetworkDisplay(), - ]), - h('.page-container__title', title), - h('.page-container__subtitle', subtitle), - ]), + this.renderHeader(isTxReprice), h('.page-container__content', [ h(SenderToRecipient, { senderName: fromName, diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js index 656093b3d..ddaa13d22 100644 --- a/ui/app/components/pending-tx/confirm-send-token.js +++ b/ui/app/components/pending-tx/confirm-send-token.js @@ -12,6 +12,7 @@ const actions = require('../../actions') const clone = require('clone') const Identicon = require('../identicon') const GasFeeDisplay = require('../send/gas-fee-display-v2.js') +const NetworkDisplay = require('../network-display') const ethUtil = require('ethereumjs-util') const BN = ethUtil.BN const { @@ -39,6 +40,11 @@ const { } = require('../../selectors') const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes') +const { + ENVIRONMENT_TYPE_POPUP, + ENVIRONMENT_TYPE_NOTIFICATION, +} = require('../../../../app/scripts/lib/enums') + ConfirmSendToken.contextTypes = { t: PropTypes.func, } @@ -430,6 +436,43 @@ ConfirmSendToken.prototype.convertToRenderableCurrency = function (value, curren : value } +ConfirmSendToken.prototype.renderHeaderRow = function (isTxReprice) { + const windowType = window.METAMASK_UI_TYPE + const isFullScreen = windowType !== ENVIRONMENT_TYPE_NOTIFICATION && + windowType !== ENVIRONMENT_TYPE_POPUP + + if (isTxReprice && isFullScreen) { + return null + } + + return ( + h('.page-container__header-row', [ + h('span.page-container__back-button', { + onClick: () => this.editTransaction(), + style: { + visibility: isTxReprice ? 'hidden' : 'initial', + }, + }, 'Edit'), + !isFullScreen && h(NetworkDisplay), + ]) + ) +} + +ConfirmSendToken.prototype.renderHeader = function (isTxReprice) { + const title = isTxReprice ? this.context.t('speedUpTitle') : this.context.t('confirm') + const subtitle = isTxReprice + ? this.context.t('speedUpSubtitle') + : this.context.t('pleaseReviewTransaction') + + return ( + h('.page-container__header', [ + this.renderHeaderRow(isTxReprice), + h('.page-container__title', title), + h('.page-container__subtitle', subtitle), + ]) + ) +} + ConfirmSendToken.prototype.render = function () { const txMeta = this.gatherTxMeta() const { @@ -443,25 +486,13 @@ ConfirmSendToken.prototype.render = function () { }, } = this.getData() - this.inputs = [] - const isTxReprice = Boolean(txMeta.lastGasPrice) - const title = isTxReprice ? this.context.t('reprice_title') : this.context.t('confirm') - const subtitle = isTxReprice - ? this.context.t('reprice_subtitle') - : this.context.t('pleaseReviewTransaction') return ( h('div.confirm-screen-container.confirm-send-token', [ // Main Send token Card h('div.page-container', [ - h('div.page-container__header', [ - !txMeta.lastGasPrice && h('button.confirm-screen-back-button', { - onClick: () => this.editTransaction(txMeta), - }, this.context.t('edit')), - h('div.page-container__title', title), - h('div.page-container__subtitle', subtitle), - ]), + this.renderHeader(isTxReprice), h('.page-container__content', [ h('div.flex-row.flex-center.confirm-screen-identicons', [ h('div.confirm-screen-account-wrapper', [ diff --git a/ui/app/components/tx-list-item.js b/ui/app/components/tx-list-item.js index bd4ea80a6..ef441ff73 100644 --- a/ui/app/components/tx-list-item.js +++ b/ui/app/components/tx-list-item.js @@ -1,5 +1,7 @@ const Component = require('react').Component const PropTypes = require('prop-types') +const { compose } = require('recompose') +const { withRouter } = require('react-router-dom') const h = require('react-hyperscript') const connect = require('react-redux').connect const inherits = require('util').inherits @@ -16,13 +18,16 @@ const { conversionUtil, multiplyCurrencies } = require('../conversion-util') const { calcTokenAmount } = require('../token-util') const { getCurrentCurrency } = require('../selectors') +const { CONFIRM_TRANSACTION_ROUTE } = require('../routes') TxListItem.contextTypes = { t: PropTypes.func, } -module.exports = connect(mapStateToProps, mapDispatchToProps)(TxListItem) - +module.exports = compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(TxListItem) function mapStateToProps (state) { return { @@ -216,6 +221,7 @@ TxListItem.prototype.setSelectedToken = function (tokenAddress) { TxListItem.prototype.resubmit = function () { const { transactionId } = this.props this.props.retryTransaction(transactionId) + .then(id => this.props.history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`)) } TxListItem.prototype.render = function () { diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index fb38aaa76..461587cb1 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -7,6 +7,7 @@ const { compose } = require('recompose') const actions = require('./actions') const txHelper = require('../lib/tx-helper') const log = require('loglevel') +const R = require('ramda') const PendingTx = require('./components/pending-tx') const SignatureRequest = require('./components/signature-request') @@ -87,37 +88,74 @@ ConfirmTxScreen.prototype.componentDidUpdate = function (prevProps) { network, selectedAddressTxList, send, + history, + match: { params: { id: transactionId } = {} }, } = this.props - const { index: prevIndex, unapprovedTxs: prevUnapprovedTxs } = prevProps - const prevUnconfTxList = txHelper(prevUnapprovedTxs, {}, {}, {}, network) - const prevTxData = prevUnconfTxList[prevIndex] || {} - const prevTx = selectedAddressTxList.find(({ id }) => id === prevTxData.id) || {} + + let prevTx + + if (transactionId) { + prevTx = R.find(({ id }) => id + '' === transactionId)(selectedAddressTxList) + } else { + const { index: prevIndex, unapprovedTxs: prevUnapprovedTxs } = prevProps + const prevUnconfTxList = txHelper(prevUnapprovedTxs, {}, {}, {}, network) + const prevTxData = prevUnconfTxList[prevIndex] || {} + prevTx = selectedAddressTxList.find(({ id }) => id === prevTxData.id) || {} + } + const unconfTxList = txHelper(unapprovedTxs, {}, {}, {}, network) - if (unconfTxList.length === 0 && - (prevTx.status === 'dropped' || !send.to && this.getUnapprovedMessagesTotal() === 0)) { + if (prevTx.status === 'dropped') { + this.props.dispatch(actions.showModal({ + name: 'TRANSACTION_CONFIRMED', + onHide: () => history.push(DEFAULT_ROUTE), + })) + + return + } + + if (unconfTxList.length === 0 && !send.to && this.getUnapprovedMessagesTotal() === 0) { this.props.history.push(DEFAULT_ROUTE) } } -ConfirmTxScreen.prototype.render = function () { - const props = this.props +ConfirmTxScreen.prototype.getTxData = function () { const { network, + index, + unapprovedTxs, + unapprovedMsgs, + unapprovedPersonalMsgs, + unapprovedTypedMessages, + match: { params: { id: transactionId } = {} }, + } = this.props + + const unconfTxList = txHelper( unapprovedTxs, - currentCurrency, unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, + network + ) + + log.info(`rendering a combined ${unconfTxList.length} unconf msgs & txs`) + + return transactionId + ? R.find(({ id }) => id + '' === transactionId)(unconfTxList) + : unconfTxList[index] +} + +ConfirmTxScreen.prototype.render = function () { + const props = this.props + const { + currentCurrency, conversionRate, blockGasLimit, // provider, // computedBalances, } = props - var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, network) - - var txData = unconfTxList[props.index] || {} + var txData = this.getTxData() || {} var txParams = txData.params || {} // var isNotification = isPopupOrNotification() === 'notification' @@ -136,7 +174,6 @@ ConfirmTxScreen.prototype.render = function () { ]), */ - log.info(`rendering a combined ${unconfTxList.length} unconf msg & txs`) return currentTxView({ // Properties diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 2b39eb8db..4e9d0848c 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -42,6 +42,7 @@ function reduceApp (state, action) { open: false, modalState: { name: null, + props: {}, }, previousModalState: { name: null, @@ -88,13 +89,17 @@ function reduceApp (state, action) { // modal methods: case actions.MODAL_OPEN: + const { name, ...modalProps } = action.payload + return extend(appState, { - modal: Object.assign( - state.appState.modal, - { open: true }, - { modalState: action.payload }, - { previousModalState: appState.modal.modalState}, - ), + modal: { + open: true, + modalState: { + name: name, + props: { ...modalProps }, + }, + previousModalState: { ...appState.modal.modalState }, + }, }) case actions.MODAL_CLOSE: @@ -102,7 +107,7 @@ function reduceApp (state, action) { modal: Object.assign( state.appState.modal, { open: false }, - { modalState: { name: null } }, + { modalState: { name: null, props: {} } }, { previousModalState: appState.modal.modalState}, ), }) From e3cc89cc7b3836bf0dc2051b420f3982413b2ffc Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Tue, 29 May 2018 21:59:53 -0230 Subject: [PATCH 21/31] Update changelog --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7574e4815..af4c886bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ ## Current Master +- Adds error messages when passwords don't match in onboarding flow. +- Adds modal notification if a retry in the process of being confirmed is dropped. +- New unlock screen design. +- Design improvements to the add token screen. +- Fix inconsistencies in confirm screen between extension and browser window modes. +- Fix scrolling in deposit ether modal. +- Fix styling of app spinner. +- Font weight changed from 300 to 400. +- New reveal screen design. +- Styling improvements to labels in first time flow and signature request headers. + ## 4.6.1 Mon Apr 30 2018 - Fix bug where sending a transaction resulted in an infinite spinner From d6965c7cdbd7c01a9901ea1125ac77000e0f8b25 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 29 May 2018 17:38:00 -0700 Subject: [PATCH 22/31] deps - bump eth-keyring-controller + update package-lock --- package-lock.json | 103 +++++++++++++++++++++++++--------------------- package.json | 2 +- 2 files changed, 56 insertions(+), 49 deletions(-) diff --git a/package-lock.json b/package-lock.json index e3e1b4eec..790e2e079 100644 --- a/package-lock.json +++ b/package-lock.json @@ -309,7 +309,7 @@ }, "@sinonjs/formatio": { "version": "2.0.0", - "resolved": "http://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz", "integrity": "sha512-ls6CAMA6/5gG+O/IdsBcblvnd8qcO/l1TYoNeAzp3wcISOxlPXQEus0mLcdwazEkWjaBdaJ3TaxmNgCLWwvWzg==", "dev": true, "requires": { @@ -1500,7 +1500,8 @@ }, "dependencies": { "bignumber.js": { - "version": "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2" + "version": "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2", + "from": "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2" }, "chai": { "version": "3.5.0", @@ -8007,6 +8008,7 @@ "dependencies": { "async-eventemitter": { "version": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", + "from": "async-eventemitter@github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", "requires": { "async": "2.6.0" } @@ -8161,20 +8163,20 @@ } }, "eth-keyring-controller": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/eth-keyring-controller/-/eth-keyring-controller-3.1.1.tgz", - "integrity": "sha512-Z9HTzrop/V4Ld8Wq7uwetKecfWIyx25/uL8aFoZxV3kegZGoXaWoRmNy+4oW0WNLp4BcJ1lk6QfsGEdlymGjmA==", - "requires": { - "bip39": "2.4.0", - "bluebird": "3.5.1", - "browser-passworder": "2.0.3", - "eth-hd-keyring": "1.2.2", - "eth-sig-util": "1.4.2", - "eth-simple-keyring": "1.2.1", - "ethereumjs-util": "5.2.0", - "loglevel": "1.6.0", - "obs-store": "2.4.1", - "promise-filter": "1.1.0" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/eth-keyring-controller/-/eth-keyring-controller-3.1.3.tgz", + "integrity": "sha512-5xzUeT2mq+S2GvTRnvhdZjrasZg+TqeSTH5sZRbDSXgXC9TYobFvEcK6g2wP/RkOQ3dt0xo/6/TWgwiXFfNZjw==", + "requires": { + "bip39": "^2.4.0", + "bluebird": "^3.5.0", + "browser-passworder": "^2.0.3", + "eth-hd-keyring": "^1.2.2", + "eth-sig-util": "^1.4.0", + "eth-simple-keyring": "^1.2.1", + "ethereumjs-util": "^5.1.2", + "loglevel": "^1.5.0", + "obs-store": "^2.4.1", + "promise-filter": "^1.1.0" }, "dependencies": { "babelify": { @@ -8182,8 +8184,8 @@ "resolved": "https://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz", "integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=", "requires": { - "babel-core": "6.26.0", - "object-assign": "4.1.1" + "babel-core": "^6.0.14", + "object-assign": "^4.0.0" } }, "ethereumjs-util": { @@ -8191,13 +8193,13 @@ "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", "requires": { - "bn.js": "4.11.8", - "create-hash": "1.1.3", - "ethjs-util": "0.1.4", - "keccak": "1.4.0", - "rlp": "2.0.0", - "safe-buffer": "5.1.1", - "secp256k1": "3.4.0" + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "ethjs-util": "^0.1.3", + "keccak": "^1.0.2", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1", + "secp256k1": "^3.0.1" } }, "obs-store": { @@ -8205,11 +8207,11 @@ "resolved": "https://registry.npmjs.org/obs-store/-/obs-store-2.4.1.tgz", "integrity": "sha512-wpA8G4uSn8cnCKZ0pFTvqsamvy0Sm1hR2ot0Qonbfj5yBMwdAp/eD4vDI+U/ZCbV1hb2V5GapL8YKUdGCvahgg==", "requires": { - "babel-preset-es2015": "6.24.1", - "babelify": "7.3.0", - "readable-stream": "2.3.3", - "through2": "2.0.3", - "xtend": "4.0.1" + "babel-preset-es2015": "^6.22.0", + "babelify": "^7.3.0", + "readable-stream": "^2.2.2", + "through2": "^2.0.3", + "xtend": "^4.0.1" } } } @@ -8256,6 +8258,7 @@ "dependencies": { "ethereumjs-abi": { "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#4ea2fdfed09e8f99117d9362d17c6b01b64a2bcf", + "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", "requires": { "bn.js": "4.11.8", "ethereumjs-util": "5.1.3" @@ -8282,11 +8285,11 @@ "resolved": "https://registry.npmjs.org/eth-simple-keyring/-/eth-simple-keyring-1.2.1.tgz", "integrity": "sha1-bXs1LcWppQINYfafryHvsvY2P0U=", "requires": { - "eth-sig-util": "1.4.2", - "ethereumjs-util": "5.2.0", - "ethereumjs-wallet": "0.6.0", - "events": "1.1.1", - "xtend": "4.0.1" + "eth-sig-util": "^1.4.2", + "ethereumjs-util": "^5.1.1", + "ethereumjs-wallet": "^0.6.0", + "events": "^1.1.1", + "xtend": "^4.0.1" }, "dependencies": { "ethereumjs-util": { @@ -8294,13 +8297,13 @@ "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", "requires": { - "bn.js": "4.11.8", - "create-hash": "1.1.3", - "ethjs-util": "0.1.4", - "keccak": "1.4.0", - "rlp": "2.0.0", - "safe-buffer": "5.1.1", - "secp256k1": "3.4.0" + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "ethjs-util": "^0.1.3", + "keccak": "^1.0.2", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1", + "secp256k1": "^3.0.1" } } } @@ -8487,7 +8490,7 @@ "eth-query": "2.1.2", "ethereumjs-block": "1.7.0", "ethereumjs-tx": "1.3.3", - "ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", + "ethereumjs-util": "^5.0.1", "ethereumjs-vm": "2.3.5", "through2": "2.0.3", "treeify": "1.1.0", @@ -8636,7 +8639,7 @@ "async": "2.6.0", "ethereum-common": "0.2.0", "ethereumjs-tx": "1.3.3", - "ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", + "ethereumjs-util": "^5.0.0", "merkle-patricia-tree": "2.3.0" } }, @@ -8646,7 +8649,7 @@ "integrity": "sha1-7OBR0+/b53GtKlGNYWMsoqt17Ls=", "requires": { "ethereum-common": "0.0.18", - "ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9" + "ethereumjs-util": "^5.0.0" }, "dependencies": { "ethereum-common": { @@ -8658,6 +8661,7 @@ }, "ethereumjs-util": { "version": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", + "from": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", "requires": { "bn.js": "4.11.8", "create-hash": "1.1.3", @@ -9095,7 +9099,7 @@ }, "event-stream": { "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", "dev": true, "requires": { @@ -11811,6 +11815,7 @@ }, "gulp": { "version": "github:gulpjs/gulp#71c094a51c7972d26f557899ddecab0210ef3776", + "from": "github:gulpjs/gulp#4.0", "requires": { "glob-watcher": "4.0.0", "gulp-cli": "2.0.1", @@ -18193,7 +18198,7 @@ "integrity": "sha512-LKd2OoIT9Re/OG38zXbd5pyHIk2IfcOUczCwkYXl5iJIbufg9nqpweh66VfPwMkUlrEvc7YVvtQdmSrB9V9TkQ==", "requires": { "async": "1.5.2", - "ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", + "ethereumjs-util": "^5.0.0", "level-ws": "0.0.0", "levelup": "1.3.9", "memdown": "1.4.1", @@ -31215,7 +31220,8 @@ }, "dependencies": { "bignumber.js": { - "version": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934" + "version": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934", + "from": "git+https://github.com/frozeman/bignumber.js-nolookahead.git" } } }, @@ -31696,6 +31702,7 @@ }, "websocket": { "version": "git://github.com/frozeman/WebSocket-Node.git#7004c39c42ac98875ab61126e5b4a925430f592c", + "from": "websocket@git://github.com/frozeman/WebSocket-Node.git#7004c39c42ac98875ab61126e5b4a925430f592c", "requires": { "debug": "2.6.9", "nan": "2.8.0", diff --git a/package.json b/package.json index 5fbbabe80..ed127e059 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "eth-hd-keyring": "^1.2.1", "eth-json-rpc-filters": "^1.2.6", "eth-json-rpc-infura": "^3.0.0", - "eth-keyring-controller": "^3.1.1", + "eth-keyring-controller": "^3.1.3", "eth-phishing-detect": "^1.1.4", "eth-query": "^2.1.2", "eth-sig-util": "^1.4.2", From f90ad6191cde32830cff4a82d3d8c6a31155bc29 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 29 May 2018 18:20:54 -0700 Subject: [PATCH 23/31] Add account --- test/e2e/metamask.spec.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/e2e/metamask.spec.js b/test/e2e/metamask.spec.js index 8ec7de16c..d2ca29104 100644 --- a/test/e2e/metamask.spec.js +++ b/test/e2e/metamask.spec.js @@ -121,6 +121,12 @@ describe('Metamask popup page', function () { await delay(300) }) + it('adds a second account', async function () { + await driver.findElement(By.css('#app-content > div > div.full-width > div > div:nth-child(2) > span > div')).click() + await delay(300) + await driver.findElement(By.css('#app-content > div > div.full-width > div > div:nth-child(2) > span > div > div > span > div > li:nth-child(3) > span')).click() + }) + it('shows account address', async function () { accountAddress = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div > div:nth-child(1) > flex-column > div.flex-row > div')).getText() }) From a19b911febbd2f29ef838a0f6059319c6d1c054e Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Tue, 29 May 2018 18:31:32 -0700 Subject: [PATCH 24/31] Add rpc key to i18n messages (#4375) --- app/_locales/en/messages.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 34b7477a6..4851508a3 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -679,6 +679,9 @@ "ropsten": { "message": "Ropsten Test Network" }, + "rpc": { + "message": "Custom RPC" + }, "currentRpc": { "message": "Current RPC" }, From 73afb263cb80a54cc52e50349c3d9f4fe173bd98 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 29 May 2018 22:13:58 -0700 Subject: [PATCH 25/31] ci - job-screens - use e2e funcs --- test/screens/new-ui.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/test/screens/new-ui.js b/test/screens/new-ui.js index 6b873ac85..65a542e49 100644 --- a/test/screens/new-ui.js +++ b/test/screens/new-ui.js @@ -11,9 +11,8 @@ const GIFEncoder = require('gifencoder') const pngFileStream = require('png-file-stream') const sizeOfPng = require('image-size/lib/types/png') const By = webdriver.By -const { delay, buildWebDriver } = require('./func') const localesIndex = require('../../app/_locales/index.json') -// const localesIndex = [] +const { delay, buildChromeWebDriver, buildFirefoxWebdriver, installWebExt, getExtensionIdChrome, getExtensionIdFirefox } = require('../e2e/func') const eth = new Ethjs(new Ethjs.HttpProvider('http://localhost:8545')) @@ -50,11 +49,10 @@ async function captureAllScreens() { await cleanScreenShotDir() - // setup selenium and install extension const extPath = path.resolve('dist/chrome') - driver = buildWebDriver(extPath) - await driver.get('chrome://extensions-frame') - const extensionId = await driver.executeScript('return document.querySelector("extensions-manager").shadowRoot.querySelector("extensions-view-manager extensions-item-list").shadowRoot.querySelector("#container > div.items-container > extensions-item:nth-child(2)").getAttribute("id")') + driver = buildChromeWebDriver(extPath) + const extensionId = await getExtensionIdChrome(driver) + await driver.get(`chrome-extension://${extensionId}/home.html`) await delay(500) tabs = await driver.getAllWindowHandles() From be6776897f16ab73fd6b363a1a9bc685c3afddcd Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 29 May 2018 23:07:45 -0700 Subject: [PATCH 26/31] Delay before getting address text --- test/e2e/metamask.spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/e2e/metamask.spec.js b/test/e2e/metamask.spec.js index d2ca29104..a08a34d96 100644 --- a/test/e2e/metamask.spec.js +++ b/test/e2e/metamask.spec.js @@ -128,6 +128,7 @@ describe('Metamask popup page', function () { }) it('shows account address', async function () { + await delay(300) accountAddress = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div > div:nth-child(1) > flex-column > div.flex-row > div')).getText() }) From d40971e7f30f86629f661a53e4c7b51f34397d87 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Wed, 30 May 2018 09:23:31 -0700 Subject: [PATCH 27/31] Fix error handling on incorrect password (#4401) --- .../unlock-page/unlock-page.component.js | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/ui/app/components/pages/unlock-page/unlock-page.component.js b/ui/app/components/pages/unlock-page/unlock-page.component.js index b6384b32d..8bc3897da 100644 --- a/ui/app/components/pages/unlock-page/unlock-page.component.js +++ b/ui/app/components/pages/unlock-page/unlock-page.component.js @@ -34,14 +34,7 @@ class UnlockPage extends Component { } } - tryUnlockMetamask (password) { - const { tryUnlockMetamask, history } = this.props - tryUnlockMetamask(password) - .catch(({ message }) => this.setState({ error: message })) - .then(() => history.push(DEFAULT_ROUTE)) - } - - handleSubmit (event) { + async handleSubmit (event) { event.preventDefault() event.stopPropagation() @@ -54,9 +47,14 @@ class UnlockPage extends Component { this.setState({ error: null }) - tryUnlockMetamask(password) - .catch(({ message }) => this.setState({ error: message })) - .then(() => history.push(DEFAULT_ROUTE)) + try { + await tryUnlockMetamask(password) + } catch ({ message }) { + this.setState({ error: message }) + return + } + + history.push(DEFAULT_ROUTE) } handleInputChange ({ target }) { From 389346913bf076fde6190a61afc4e3e4cd210afc Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Wed, 30 May 2018 10:38:53 -0700 Subject: [PATCH 28/31] Prevent loading screen from overlaying the app bar (#4417) --- ui/app/app.js | 1 - .../components/loading-screen/loading-screen.component.js | 6 +----- ui/app/components/pending-tx/index.js | 5 +---- ui/app/css/itcss/components/loading-overlay.scss | 4 ++-- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/ui/app/app.js b/ui/app/app.js index aa2b24422..0e8b907df 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -137,7 +137,6 @@ class App extends Component { (isLoading || isLoadingNetwork) && h(Loading, { loadingMessage: loadMessage, - fullScreen: true, }), // content diff --git a/ui/app/components/loading-screen/loading-screen.component.js b/ui/app/components/loading-screen/loading-screen.component.js index bce2a4aac..6b843cfee 100644 --- a/ui/app/components/loading-screen/loading-screen.component.js +++ b/ui/app/components/loading-screen/loading-screen.component.js @@ -1,7 +1,6 @@ const { Component } = require('react') const h = require('react-hyperscript') const PropTypes = require('prop-types') -const classnames = require('classnames') const Spinner = require('../spinner') class LoadingScreen extends Component { @@ -12,9 +11,7 @@ class LoadingScreen extends Component { render () { return ( - h('.loading-overlay', { - className: classnames({ 'loading-overlay--full-screen': this.props.fullScreen }), - }, [ + h('.loading-overlay', [ h('.loading-overlay__container', [ h(Spinner, { color: '#F7C06C', @@ -29,7 +26,6 @@ class LoadingScreen extends Component { LoadingScreen.propTypes = { loadingMessage: PropTypes.string, - fullScreen: PropTypes.bool, } module.exports = LoadingScreen diff --git a/ui/app/components/pending-tx/index.js b/ui/app/components/pending-tx/index.js index 893538bcf..3f8cd8823 100644 --- a/ui/app/components/pending-tx/index.js +++ b/ui/app/components/pending-tx/index.js @@ -130,7 +130,6 @@ PendingTx.prototype.render = function () { if (isFetching) { return h(Loading, { - fullScreen: true, loadingMessage: this.context.t('generatingTransaction'), }) } @@ -157,9 +156,7 @@ PendingTx.prototype.render = function () { sendTransaction, }) default: - return h(Loading, { - fullScreen: true, - }) + return h(Loading) } } diff --git a/ui/app/css/itcss/components/loading-overlay.scss b/ui/app/css/itcss/components/loading-overlay.scss index c18b7fa59..b07d6af17 100644 --- a/ui/app/css/itcss/components/loading-overlay.scss +++ b/ui/app/css/itcss/components/loading-overlay.scss @@ -11,8 +11,8 @@ background: rgba(255, 255, 255, .8); @media screen and (max-width: 575px) { - margin-top: 56px; - height: calc(100% - 56px); + margin-top: 66px; + height: calc(100% - 66px); } @media screen and (min-width: 576px) { From b69da50095ff5077428e08cbc20d7dd839518884 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 30 May 2018 14:29:14 -0700 Subject: [PATCH 29/31] deps - bump eth-keyring-controller for bugfix --- package-lock.json | 20 +++++++++++++------- package.json | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index f501b923c..4b27c769e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8169,16 +8169,17 @@ } }, "eth-keyring-controller": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/eth-keyring-controller/-/eth-keyring-controller-3.1.3.tgz", - "integrity": "sha512-5xzUeT2mq+S2GvTRnvhdZjrasZg+TqeSTH5sZRbDSXgXC9TYobFvEcK6g2wP/RkOQ3dt0xo/6/TWgwiXFfNZjw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/eth-keyring-controller/-/eth-keyring-controller-3.1.4.tgz", + "integrity": "sha512-NNlVB/TBc8p9CblwECjPlUR+7MNQKiBa7tEFxIzZ9MjjNCEYPWDXTm0vJZzuDtVmFxYwIA53UD0QEn0QNxWNEQ==", + "dev": true, "requires": { "bip39": "^2.4.0", "bluebird": "^3.5.0", "browser-passworder": "^2.0.3", "eth-hd-keyring": "^1.2.2", "eth-sig-util": "^1.4.0", - "eth-simple-keyring": "^1.2.1", + "eth-simple-keyring": "^1.2.2", "ethereumjs-util": "^5.1.2", "loglevel": "^1.5.0", "obs-store": "^2.4.1", @@ -8189,6 +8190,7 @@ "version": "7.3.0", "resolved": "https://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz", "integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=", + "dev": true, "requires": { "babel-core": "^6.0.14", "object-assign": "^4.0.0" @@ -8198,6 +8200,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", + "dev": true, "requires": { "bn.js": "^4.11.0", "create-hash": "^1.1.2", @@ -8212,6 +8215,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/obs-store/-/obs-store-2.4.1.tgz", "integrity": "sha512-wpA8G4uSn8cnCKZ0pFTvqsamvy0Sm1hR2ot0Qonbfj5yBMwdAp/eD4vDI+U/ZCbV1hb2V5GapL8YKUdGCvahgg==", + "dev": true, "requires": { "babel-preset-es2015": "^6.22.0", "babelify": "^7.3.0", @@ -8287,9 +8291,10 @@ } }, "eth-simple-keyring": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/eth-simple-keyring/-/eth-simple-keyring-1.2.1.tgz", - "integrity": "sha1-bXs1LcWppQINYfafryHvsvY2P0U=", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/eth-simple-keyring/-/eth-simple-keyring-1.2.2.tgz", + "integrity": "sha512-uQVBYshHUOaXVoat1BpLA/QNMCr4hgdFBgwIB7rRmQ+m3vQQAseUsOM+biPDYzq6end+6LjcccElLpQaIZe6dg==", + "dev": true, "requires": { "eth-sig-util": "^1.4.2", "ethereumjs-util": "^5.1.1", @@ -8302,6 +8307,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", + "dev": true, "requires": { "bn.js": "^4.11.0", "create-hash": "^1.1.2", diff --git a/package.json b/package.json index 0b0ac839a..ba764f45b 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,6 @@ "eth-hd-keyring": "^1.2.1", "eth-json-rpc-filters": "^1.2.6", "eth-json-rpc-infura": "^3.0.0", - "eth-keyring-controller": "^3.1.3", "eth-phishing-detect": "^1.1.4", "eth-query": "^2.1.2", "eth-sig-util": "^1.4.2", @@ -230,6 +229,7 @@ "eslint-plugin-mocha": "^5.0.0", "eslint-plugin-react": "^7.4.0", "eth-json-rpc-middleware": "^1.6.0", + "eth-keyring-controller": "^3.1.4", "file-loader": "^1.1.11", "fs-promise": "^2.0.3", "ganache-cli": "^6.1.0", From f4d833cb09758beb62a65ad4011d16bdb81b33ff Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Tue, 29 May 2018 14:33:29 -0700 Subject: [PATCH 30/31] Change btn-secondary styles to btn-default. Make btn-secondary red warning style buttons --- ui/app/components/button/button.component.js | 23 +++++---- ui/app/components/button/button.stories.js | 19 ++++++- .../components/customize-gas-modal/index.js | 2 +- .../components/modals/deposit-ether-modal.js | 2 +- .../modals/export-private-key-modal.js | 6 +-- .../pages/add-token/add-token.component.js | 2 +- .../confirm-add-token.component.js | 2 +- .../create-account/import-account/json.js | 4 +- .../import-account/private-key.js | 6 +-- .../pages/create-account/new-account.js | 4 +- .../components/pages/keychains/reveal-seed.js | 6 +-- ui/app/components/pages/settings/settings.js | 8 +-- ui/app/components/shapeshift-form.js | 2 +- ui/app/components/signature-request.js | 4 +- ui/app/css/itcss/components/buttons.scss | 49 ++++++++++--------- ui/app/send-v2.js | 2 +- 16 files changed, 79 insertions(+), 62 deletions(-) diff --git a/ui/app/components/button/button.component.js b/ui/app/components/button/button.component.js index fe3bf363c..e8e798445 100644 --- a/ui/app/components/button/button.component.js +++ b/ui/app/components/button/button.component.js @@ -2,20 +2,15 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import classnames from 'classnames' -const SECONDARY = 'secondary' +const CLASSNAME_DEFAULT = 'btn-default' const CLASSNAME_PRIMARY = 'btn-primary' -const CLASSNAME_PRIMARY_LARGE = 'btn-primary--lg' const CLASSNAME_SECONDARY = 'btn-secondary' -const CLASSNAME_SECONDARY_LARGE = 'btn-secondary--lg' +const CLASSNAME_LARGE = 'btn--large' -const getClassName = (type, large = false) => { - let output = type === SECONDARY ? CLASSNAME_SECONDARY : CLASSNAME_PRIMARY - - if (large) { - output += ` ${type === SECONDARY ? CLASSNAME_SECONDARY_LARGE : CLASSNAME_PRIMARY_LARGE}` - } - - return output +const typeHash = { + default: CLASSNAME_DEFAULT, + primary: CLASSNAME_PRIMARY, + secondary: CLASSNAME_SECONDARY, } class Button extends Component { @@ -24,7 +19,11 @@ class Button extends Component { return ( ) - .add('secondary', () => ( + .add('secondary', () => + ) + .add('default', () => ( + )) .add('large primary', () => ( + )) diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js index 1ff8eea87..e3529041b 100644 --- a/ui/app/components/customize-gas-modal/index.js +++ b/ui/app/components/customize-gas-modal/index.js @@ -308,7 +308,7 @@ CustomizeGasModal.prototype.render = function () { }, [this.context.t('revert')]), h('div.send-v2__customize-gas__buttons', [ - h('button.btn-secondary.send-v2__customize-gas__cancel', { + h('button.btn-default.send-v2__customize-gas__cancel', { onClick: this.props.hideModal, style: { marginRight: '10px', diff --git a/ui/app/components/modals/deposit-ether-modal.js b/ui/app/components/modals/deposit-ether-modal.js index ad5f9b695..2daa7fa1d 100644 --- a/ui/app/components/modals/deposit-ether-modal.js +++ b/ui/app/components/modals/deposit-ether-modal.js @@ -109,7 +109,7 @@ DepositEtherModal.prototype.renderRow = function ({ ]), !hideButton && h('div.deposit-ether-modal__buy-row__button', [ - h('button.btn-primary--lg.deposit-ether-modal__deposit-button', { + h('button.btn-primary.btn--large.deposit-ether-modal__deposit-button', { onClick: onButtonClick, }, [buttonLabel]), ]), diff --git a/ui/app/components/modals/export-private-key-modal.js b/ui/app/components/modals/export-private-key-modal.js index 447e43b7a..80ece425f 100644 --- a/ui/app/components/modals/export-private-key-modal.js +++ b/ui/app/components/modals/export-private-key-modal.js @@ -87,14 +87,14 @@ ExportPrivateKeyModal.prototype.renderButton = function (className, onClick, lab ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, password, address, hideModal) { return h('div.export-private-key-buttons', {}, [ !privateKey && this.renderButton( - 'btn-secondary--lg export-private-key__button export-private-key__button--cancel', + 'btn-default btn--large export-private-key__button export-private-key__button--cancel', () => hideModal(), 'Cancel' ), (privateKey - ? this.renderButton('btn-primary--lg export-private-key__button', () => hideModal(), this.context.t('done')) - : this.renderButton('btn-primary--lg export-private-key__button', () => this.exportAccountAndGetPrivateKey(this.state.password, address), this.context.t('confirm')) + ? this.renderButton('btn-primary btn--large export-private-key__button', () => hideModal(), this.context.t('done')) + : this.renderButton('btn-primary btn--large export-private-key__button', () => this.exportAccountAndGetPrivateKey(this.state.password, address), this.context.t('confirm')) ), ]) diff --git a/ui/app/components/pages/add-token/add-token.component.js b/ui/app/components/pages/add-token/add-token.component.js index 0677b4317..1f4b37b53 100644 --- a/ui/app/components/pages/add-token/add-token.component.js +++ b/ui/app/components/pages/add-token/add-token.component.js @@ -323,7 +323,7 @@ class AddToken extends Component {
+ +
+ + ) + } +} + +export default ConfirmResetAccount diff --git a/ui/app/components/modals/confirm-reset-account/confirm-reset-account.container.js b/ui/app/components/modals/confirm-reset-account/confirm-reset-account.container.js new file mode 100644 index 000000000..9630a5593 --- /dev/null +++ b/ui/app/components/modals/confirm-reset-account/confirm-reset-account.container.js @@ -0,0 +1,13 @@ +import { connect } from 'react-redux' +import ConfirmResetAccount from './confirm-reset-account.component' + +const { hideModal, resetAccount } = require('../../../actions') + +const mapDispatchToProps = dispatch => { + return { + hideModal: () => dispatch(hideModal()), + resetAccount: () => dispatch(resetAccount()), + } +} + +export default connect(null, mapDispatchToProps)(ConfirmResetAccount) diff --git a/ui/app/components/modals/confirm-reset-account/index.js b/ui/app/components/modals/confirm-reset-account/index.js new file mode 100644 index 000000000..c812ffc55 --- /dev/null +++ b/ui/app/components/modals/confirm-reset-account/index.js @@ -0,0 +1,2 @@ +import ConfirmResetAccount from './confirm-reset-account.container' +module.exports = ConfirmResetAccount diff --git a/ui/app/components/modals/index.scss b/ui/app/components/modals/index.scss index ec6207f7e..ad6fe16d3 100644 --- a/ui/app/components/modals/index.scss +++ b/ui/app/components/modals/index.scss @@ -1 +1,52 @@ -@import './transaction-confirmed/index'; +.modal-container { + width: 100%; + height: 100%; + background-color: #fff; + display: flex; + flex-flow: column; + border-radius: 8px; + + &__title { + font-size: 1.5rem; + font-weight: 500; + padding: 16px 0; + text-align: center; + } + + &__description { + text-align: center; + font-size: .875rem; + } + + &__content { + overflow-y: auto; + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + padding: 32px; + + @media screen and (max-width: 575px) { + justify-content: center; + padding: 28px 20px; + } + } + + &__footer { + display: flex; + flex-flow: row; + justify-content: center; + border-top: 1px solid #d2d8dd; + padding: 16px; + flex: 0 0 auto; + + &-button { + min-width: 0; + margin-right: 16px; + + &:last-of-type { + margin-right: 0; + } + } + } +} diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js index 841189277..85e85597a 100644 --- a/ui/app/components/modals/modal.js +++ b/ui/app/components/modals/modal.js @@ -19,8 +19,30 @@ const ShapeshiftDepositTxModal = require('./shapeshift-deposit-tx-modal.js') const HideTokenConfirmationModal = require('./hide-token-confirmation-modal') const CustomizeGasModal = require('../customize-gas-modal') const NotifcationModal = require('./notification-modal') -const ConfirmResetAccount = require('./notification-modals/confirm-reset-account') +const ConfirmResetAccount = require('./confirm-reset-account') const TransactionConfirmed = require('./transaction-confirmed') +const WelcomeBeta = require('./welcome-beta') +const Notification = require('./notification') + +const modalContainerBaseStyle = { + transform: 'translate3d(-50%, 0, 0px)', + border: '1px solid #CCCFD1', + borderRadius: '8px', + backgroundColor: '#FFFFFF', + boxShadow: '0 2px 22px 0 rgba(0,0,0,0.2)', +} + +const modalContainerLaptopStyle = { + ...modalContainerBaseStyle, + width: '344px', + top: '15%', +} + +const modalContainerMobileStyle = { + ...modalContainerBaseStyle, + width: '309px', + top: '12.5%', +} const accountModalStyle = { mobileModalStyle: { @@ -174,18 +196,18 @@ const MODALS = { BETA_UI_NOTIFICATION_MODAL: { contents: [ - h(NotifcationModal, { - header: 'uiWelcome', - message: 'uiWelcomeMessage', - }), + h(Notification, [ + h(WelcomeBeta), + ]), ], mobileModalStyle: { - width: '95%', - top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh', + ...modalContainerMobileStyle, }, laptopModalStyle: { - width: '449px', - top: 'calc(33% + 45px)', + ...modalContainerLaptopStyle, + }, + contentStyle: { + borderRadius: '8px', }, }, @@ -209,12 +231,13 @@ const MODALS = { CONFIRM_RESET_ACCOUNT: { contents: h(ConfirmResetAccount), mobileModalStyle: { - width: '95%', - top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh', + ...modalContainerMobileStyle, }, laptopModalStyle: { - width: '473px', - top: 'calc(33% + 45px)', + ...modalContainerLaptopStyle, + }, + contentStyle: { + borderRadius: '8px', }, }, @@ -269,31 +292,18 @@ const MODALS = { TRANSACTION_CONFIRMED: { disableBackdropClick: true, contents: [ - h(TransactionConfirmed, {}, []), + h(Notification, [ + h(TransactionConfirmed), + ]), ], mobileModalStyle: { - width: '100%', - height: '100%', - transform: 'none', - left: '0', - right: '0', - margin: '0 auto', - boxShadow: '0 0 7px 0 rgba(0,0,0,0.08)', - top: '0', - display: 'flex', + ...modalContainerMobileStyle, }, laptopModalStyle: { - width: '344px', - transform: 'translate3d(-50%, 0, 0px)', - top: '15%', - border: '1px solid #CCCFD1', - borderRadius: '8px', - backgroundColor: '#FFFFFF', - boxShadow: '0 2px 22px 0 rgba(0,0,0,0.2)', + ...modalContainerLaptopStyle, }, contentStyle: { borderRadius: '8px', - height: '100%', }, }, diff --git a/ui/app/components/modals/notification-modals/confirm-reset-account.js b/ui/app/components/modals/notification-modals/confirm-reset-account.js deleted file mode 100644 index 89fa9bef1..000000000 --- a/ui/app/components/modals/notification-modals/confirm-reset-account.js +++ /dev/null @@ -1,46 +0,0 @@ -const { Component } = require('react') -const PropTypes = require('prop-types') -const h = require('react-hyperscript') -const connect = require('react-redux').connect -const actions = require('../../../actions') -const NotifcationModal = require('../notification-modal') - -class ConfirmResetAccount extends Component { - render () { - const { resetAccount } = this.props - - return h(NotifcationModal, { - header: 'Are you sure you want to reset account?', - message: h('div', [ - - h('span', `Resetting is for developer use only. This button wipes the current account's transaction history, - which is used to calculate the current account nonce. `), - - h('a.notification-modal__link', { - href: 'http://metamask.helpscoutdocs.com/article/36-resetting-an-account', - target: '_blank', - onClick (event) { global.platform.openWindow({ url: event.target.href }) }, - }, 'Read more.'), - - ]), - showCancelButton: true, - showConfirmButton: true, - onConfirm: resetAccount, - - }) - } -} - -ConfirmResetAccount.propTypes = { - resetAccount: PropTypes.func, -} - -const mapDispatchToProps = dispatch => { - return { - resetAccount: () => { - dispatch(actions.resetAccount()) - }, - } -} - -module.exports = connect(null, mapDispatchToProps)(ConfirmResetAccount) diff --git a/ui/app/components/modals/notification/index.js b/ui/app/components/modals/notification/index.js new file mode 100644 index 000000000..d60a3129b --- /dev/null +++ b/ui/app/components/modals/notification/index.js @@ -0,0 +1,2 @@ +import Notification from './notification.container' +module.exports = Notification diff --git a/ui/app/components/modals/notification/notification.component.js b/ui/app/components/modals/notification/notification.component.js new file mode 100644 index 000000000..1af2f3ca8 --- /dev/null +++ b/ui/app/components/modals/notification/notification.component.js @@ -0,0 +1,30 @@ +import React from 'react' +import PropTypes from 'prop-types' +import Button from '../../button' + +const Notification = (props, context) => { + return ( +
+ { props.children } +
+ +
+
+ ) +} + +Notification.propTypes = { + onHide: PropTypes.func.isRequired, + children: PropTypes.element, +} + +Notification.contextTypes = { + t: PropTypes.func, +} + +export default Notification diff --git a/ui/app/components/modals/notification/notification.container.js b/ui/app/components/modals/notification/notification.container.js new file mode 100644 index 000000000..5b98714da --- /dev/null +++ b/ui/app/components/modals/notification/notification.container.js @@ -0,0 +1,38 @@ +import { connect } from 'react-redux' +import Notification from './notification.component' + +const { hideModal } = require('../../../actions') + +const mapStateToProps = state => { + const { appState: { modal: { modalState: { props } } } } = state + const { onHide } = props + return { + onHide, + } +} + +const mapDispatchToProps = dispatch => { + return { + hideModal: () => dispatch(hideModal()), + } +} + +const mergeProps = (stateProps, dispatchProps, ownProps) => { + const { onHide, ...otherStateProps } = stateProps + const { hideModal, ...otherDispatchProps } = dispatchProps + + return { + ...otherStateProps, + ...otherDispatchProps, + ...ownProps, + onHide: () => { + hideModal() + + if (onHide && typeof onHide === 'function') { + onHide() + } + }, + } +} + +export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(Notification) diff --git a/ui/app/components/modals/transaction-confirmed/index.js b/ui/app/components/modals/transaction-confirmed/index.js index c8db91388..cee8da7f8 100644 --- a/ui/app/components/modals/transaction-confirmed/index.js +++ b/ui/app/components/modals/transaction-confirmed/index.js @@ -1,2 +1,2 @@ -import TransactionConfirmed from './transaction-confirmed.container' +import TransactionConfirmed from './transaction-confirmed.component' module.exports = TransactionConfirmed diff --git a/ui/app/components/modals/transaction-confirmed/index.scss b/ui/app/components/modals/transaction-confirmed/index.scss deleted file mode 100644 index f8cd1f212..000000000 --- a/ui/app/components/modals/transaction-confirmed/index.scss +++ /dev/null @@ -1,21 +0,0 @@ -.transaction-confirmed { - display: flex; - flex-direction: column; - align-items: center; - padding: 32px; - - &__title { - font-size: 2rem; - padding: 16px 0; - } - - &__description { - text-align: center; - font-size: .875rem; - line-height: 1.5rem; - } - - @media screen and (max-width: 575px) { - justify-content: center; - } -} diff --git a/ui/app/components/modals/transaction-confirmed/transaction-confirmed.component.js b/ui/app/components/modals/transaction-confirmed/transaction-confirmed.component.js index 8d3b288ae..c1c8a2976 100644 --- a/ui/app/components/modals/transaction-confirmed/transaction-confirmed.component.js +++ b/ui/app/components/modals/transaction-confirmed/transaction-confirmed.component.js @@ -1,42 +1,20 @@ -import React, { Component } from 'react' +import React from 'react' import PropTypes from 'prop-types' -import Button from '../../button' -class TransactionConfirmed extends Component { - render () { - const { t } = this.context +const TransactionConfirmed = (props, context) => { + const { t } = context - return ( -
-
- -
- { `${t('confirmed')}!` } -
-
- { t('initialTransactionConfirmed') } -
-
-
- -
+ return ( +
+ +
+ { `${t('confirmed')}!` }
- ) - } -} - -TransactionConfirmed.propTypes = { - hideModal: PropTypes.func.isRequired, - onHide: PropTypes.func.isRequired, +
+ { t('initialTransactionConfirmed') } +
+
+ ) } TransactionConfirmed.contextTypes = { diff --git a/ui/app/components/modals/transaction-confirmed/transaction-confirmed.container.js b/ui/app/components/modals/transaction-confirmed/transaction-confirmed.container.js deleted file mode 100644 index 63872f7f2..000000000 --- a/ui/app/components/modals/transaction-confirmed/transaction-confirmed.container.js +++ /dev/null @@ -1,20 +0,0 @@ -import { connect } from 'react-redux' -import TransactionConfirmed from './transaction-confirmed.component' - -const { hideModal } = require('../../../actions') - -const mapStateToProps = state => { - const { appState: { modal: { modalState: { props } } } } = state - const { onHide } = props - return { - onHide, - } -} - -const mapDispatchToProps = dispatch => { - return { - hideModal: () => dispatch(hideModal()), - } -} - -export default connect(mapStateToProps, mapDispatchToProps)(TransactionConfirmed) diff --git a/ui/app/components/modals/welcome-beta/index.js b/ui/app/components/modals/welcome-beta/index.js new file mode 100644 index 000000000..515c9cdaf --- /dev/null +++ b/ui/app/components/modals/welcome-beta/index.js @@ -0,0 +1,2 @@ +import WelcomeBeta from './welcome-beta.component' +module.exports = WelcomeBeta diff --git a/ui/app/components/modals/welcome-beta/welcome-beta.component.js b/ui/app/components/modals/welcome-beta/welcome-beta.component.js new file mode 100644 index 000000000..61571723a --- /dev/null +++ b/ui/app/components/modals/welcome-beta/welcome-beta.component.js @@ -0,0 +1,23 @@ +import React from 'react' +import PropTypes from 'prop-types' + +const TransactionConfirmed = (props, context) => { + const { t } = context + + return ( +
+
+ { `${t('uiWelcome')}` } +
+
+ { t('uiWelcomeMessage') } +
+
+ ) +} + +TransactionConfirmed.contextTypes = { + t: PropTypes.func, +} + +export default TransactionConfirmed