aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/_locales/en/messages.json6
-rw-r--r--app/images/double-arrow.svg13
-rw-r--r--app/images/single-arrow.svg10
-rw-r--r--app/scripts/background.js1
-rw-r--r--development/states/navigate-txs.json323
-rw-r--r--test/e2e/beta/from-import-beta-ui.spec.js1
-rw-r--r--test/e2e/beta/metamask-beta-ui.spec.js146
-rw-r--r--test/integration/lib/navigate-txs.js87
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container-header/index.scss2
-rwxr-xr-xui/app/components/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js69
-rwxr-xr-xui/app/components/confirm-page-container/confirm-page-container-navigation/index.js1
-rwxr-xr-xui/app/components/confirm-page-container/confirm-page-container-navigation/index.scss54
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container.component.js35
-rw-r--r--ui/app/components/confirm-page-container/index.js2
-rw-r--r--ui/app/components/confirm-page-container/index.scss2
-rw-r--r--ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js40
-rw-r--r--ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js5
-rw-r--r--ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js10
-rw-r--r--ui/app/ducks/confirm-transaction.duck.js7
19 files changed, 795 insertions, 19 deletions
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 95c9efeeb..7a70937a3 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -785,6 +785,9 @@
"noWebcamFound": {
"message": "Your computer's webcam was not found. Please try again."
},
+ "ofTextNofM": {
+ "message": "of"
+ },
"oldUI": {
"message": "Old UI"
},
@@ -932,6 +935,9 @@
"restoreAccountWithSeed": {
"message": "Restore your Account with Seed Phrase"
},
+ "requestsAwaitingAcknowledgement": {
+ "message": "requests waiting to be acknowledged"
+ },
"required": {
"message": "Required"
},
diff --git a/app/images/double-arrow.svg b/app/images/double-arrow.svg
new file mode 100644
index 000000000..a31a0550b
--- /dev/null
+++ b/app/images/double-arrow.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="12px" height="8px" viewBox="0 0 12 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
+ <title>first/last</title>
+ <desc>Created with Sketch.</desc>
+ <defs></defs>
+ <g id="Action-Screens" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <g id="first/last" fill="#5F5C5D">
+ <polygon id="Path-12-Copy" points="12 0 12 8 6 4"></polygon>
+ <polygon id="Path-12-Copy-2" points="6 0 6 8 0 4"></polygon>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/app/images/single-arrow.svg b/app/images/single-arrow.svg
new file mode 100644
index 000000000..399da72d6
--- /dev/null
+++ b/app/images/single-arrow.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="6px" height="8px" viewBox="0 0 6 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
+ <title>previous/next</title>
+ <desc>Created with Sketch.</desc>
+ <defs></defs>
+ <g id="Action-Screens" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <polygon id="previous/next" fill="#5F5C5D" points="6 0 6 8 0 4"></polygon>
+ </g>
+</svg> \ No newline at end of file
diff --git a/app/scripts/background.js b/app/scripts/background.js
index 078e84928..a6fc5ed78 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -440,6 +440,7 @@ function triggerUi () {
const currentlyActiveMetamaskTab = Boolean(tabs.find(tab => openMetamaskTabsIDs[tab.id]))
if (!popupIsOpen && !currentlyActiveMetamaskTab && !notificationIsOpen) {
notificationManager.showPopup()
+ notificationIsOpen = true
}
})
}
diff --git a/development/states/navigate-txs.json b/development/states/navigate-txs.json
new file mode 100644
index 000000000..d8b8dc67f
--- /dev/null
+++ b/development/states/navigate-txs.json
@@ -0,0 +1,323 @@
+{
+ "metamask": {
+ "isInitialized": true,
+ "isUnlocked": true,
+ "isAccountMenuOpen": false,
+ "isMascara": false,
+ "isPopup": false,
+ "rpcTarget": "https://rawtestrpc.metamask.io/",
+ "identities": {
+ "0x8cf82b5aa41ff2282427be151dd328568684007a": {
+ "address": "0x8cf82b5aa41ff2282427be151dd328568684007a",
+ "name": "Account 3"
+ },
+ "0xbe1a00e10ec68b154adb84e8119167146a71c9a2": {
+ "address": "0xbe1a00e10ec68b154adb84e8119167146a71c9a2",
+ "name": "Account 2"
+ },
+ "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2": {
+ "address": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2",
+ "name": "Account 1"
+ }
+ },
+ "unapprovedTxs": {
+ "2389644572638771": {
+ "estimatedGas": "0x8544",
+ "gasLimitSpecified": true,
+ "gasPriceSpecified": true,
+ "history": [],
+ "id": 2389644572638771,
+ "loadingDefaults": false,
+ "metamaskNetworkId": "4",
+ "origin": "MetaMask",
+ "status": "unapproved",
+ "time": 1538844175144,
+ "txParams": {
+ "data": "0xa9059cbb000000000000000000000000be1a00e10ec68b154adb84e8119167146a71c9a20000000000000000000000000000000000000000000000000000000000000000",
+ "from": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2",
+ "gas": "0x8544",
+ "gasPrice": "0x3b9aca00",
+ "to": "0xe0b7927c4af23765cb51314a0e0521a9645f0e2a",
+ "value": "0x0"
+ },
+ "type": "standard"
+ },
+ "2389644572638772": {
+ "estimatedGas": "0x5208",
+ "gasLimitSpecified": true,
+ "gasPriceSpecified": true,
+ "history": [],
+ "id": 2389644572638772,
+ "loadingDefaults": false,
+ "metamaskNetworkId": "4",
+ "origin": "MetaMask",
+ "status": "unapproved",
+ "time": 1538844178492,
+ "txParams": {
+ "from": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2",
+ "gas": "0x5208",
+ "gasPrice": "0x3b9aca00",
+ "to": "0xbe1a00e10ec68b154adb84e8119167146a71c9a2",
+ "value": "0x0"
+ },
+ "type": "standard"
+ },
+ "2389644572638773": {
+ "estimatedGas": {
+ "length": 1,
+ "negative": 0,
+ "red": null,
+ "words": [
+ 34061,
+ null
+ ]
+ },
+ "gasLimitSpecified": false,
+ "gasPriceSpecified": true,
+ "history": [],
+ "id": 2389644572638773,
+ "loadingDefaults": false,
+ "metamaskNetworkId": "4",
+ "origin": "localhost",
+ "status": "unapproved",
+ "time": 1538844204724,
+ "txParams": {
+ "data": "0xdfb29935000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000155468697320697320746865206970667320686173680000000000000000000000",
+ "from": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2",
+ "gas": "0xc793",
+ "gasPrice": "0x3b9aca00",
+ "to": "0xb7ec370c889b3b48ec537e0b2c887faedceb254a",
+ "value": "0x0"
+ },
+ "type": "standard"
+ },
+ "2389644572638774": {
+ "estimatedGas": "0x38f53",
+ "gasLimitSpecified": true,
+ "gasPriceSpecified": false,
+ "history": [],
+ "id": 2389644572638774,
+ "loadingDefaults": false,
+ "metamaskNetworkId": "4",
+ "origin": "remix.ethereum.org",
+ "status": "unapproved",
+ "time": 1538844223352,
+ "txParams": {
+ "data": "0x608060405234801561001057600080fd5b506102a7806100206000396000f30060806040526004361061004b5763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663d13319c48114610050578063dfb29935146100da575b600080fd5b34801561005c57600080fd5b50610065610135565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561009f578181015183820152602001610087565b50505050905090810190601f1680156100cc5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156100e657600080fd5b506040805160206004803580820135601f81018490048402850184019095528484526101339436949293602493928401919081908401838280828437509497506101cc9650505050505050565b005b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156101c15780601f10610196576101008083540402835291602001916101c1565b820191906000526020600020905b8154815290600101906020018083116101a457829003601f168201915b505050505090505b90565b80516101df9060009060208401906101e3565b5050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061022457805160ff1916838001178555610251565b82800160010185558215610251579182015b82811115610251578251825591602001919060010190610236565b5061025d929150610261565b5090565b6101c991905b8082111561025d57600081556001016102675600a165627a7a72305820cf4282c534b8f2faad947d592afa109b907e4e6b2f52335b361b69c24fedb9580029",
+ "from": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2",
+ "gas": "0x38f53",
+ "gasPrice": "0x3b9aca00",
+ "value": "0x0"
+ },
+ "type": "standard"
+ }
+ },
+ "noActiveNotices": true,
+ "frequentRpcList": [],
+ "addressBook": [],
+ "selectedTokenAddress": null,
+ "contractExchangeRates": {},
+ "tokenExchangeRates": {},
+ "tokens": [
+ {
+ "address": "0xe0b7927c4af23765cb51314a0e0521a9645f0e2a",
+ "decimals": 9,
+ "symbol": "DGD"
+ }
+ ],
+ "pendingTokens": {},
+ "send": {
+ "gasLimit": null,
+ "gasPrice": null,
+ "gasTotal": null,
+ "tokenBalance": null,
+ "from": "",
+ "to": "",
+ "amount": "0x0",
+ "memo": "",
+ "errors": {},
+ "editingTransactionId": null,
+ "forceGasMin": null
+ },
+ "coinOptions": {},
+ "useBlockie": false,
+ "featureFlags": {
+ "betaUI": true,
+ "skipAnnounceBetaUI": true
+ },
+ "isRevealingSeedWords": false,
+ "welcomeScreenSeen": false,
+ "currentLocale": "en",
+ "preferences": {
+ "useETHAsPrimaryCurrency": true
+ },
+ "provider": {
+ "type": "rinkeby"
+ },
+ "network": "4",
+ "accounts": {
+ "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2": {
+ "address": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2",
+ "balance": "0x36aabfb2a0190c00"
+ },
+ "0xbe1a00e10ec68b154adb84e8119167146a71c9a2": {
+ "address": "0xbe1a00e10ec68b154adb84e8119167146a71c9a2",
+ "balance": "0x7b3ef08c294a000"
+ },
+ "0x8cf82b5aa41ff2282427be151dd328568684007a": {
+ "address": "0x8cf82b5aa41ff2282427be151dd328568684007a",
+ "balance": "0x0"
+ }
+ },
+ "currentBlockGasLimit": "0x731e25",
+ "selectedAddressTxList": [],
+ "computedBalances": {},
+ "unapprovedMsgs": {},
+ "unapprovedMsgCount": 0,
+ "unapprovedPersonalMsgs": {},
+ "unapprovedPersonalMsgCount": 0,
+ "unapprovedTypedMessages": {},
+ "unapprovedTypedMessagesCount": 0,
+ "keyringTypes": [
+ "Simple Key Pair",
+ "HD Key Tree",
+ "Trezor Hardware",
+ "Ledger Hardware"
+ ],
+ "keyrings": [
+ {
+ "type": "HD Key Tree",
+ "accounts": [
+ "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2",
+ "0xbe1a00e10ec68b154adb84e8119167146a71c9a2",
+ "0x8cf82b5aa41ff2282427be151dd328568684007a"
+ ]
+ }
+ ],
+ "currentAccountTab": "history",
+ "accountTokens": {
+ "0x8cf82b5aa41ff2282427be151dd328568684007a": {},
+ "0xbe1a00e10ec68b154adb84e8119167146a71c9a2": {},
+ "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2": {
+ "rinkeby": [
+ {
+ "address": "0xe0b7927c4af23765cb51314a0e0521a9645f0e2a",
+ "decimals": 9,
+ "symbol": "DGD"
+ },
+ {
+ "address": "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359",
+ "decimals": 18,
+ "symbol": "DAI"
+ }
+ ]
+ }
+ },
+ "assetImages": {
+ "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359": null,
+ "0xe0b7927c4af23765cb51314a0e0521a9645f0e2a": null
+ },
+ "suggestedTokens": {},
+ "lostIdentities": {},
+ "seedWords": null,
+ "forgottenPassword": false,
+ "selectedAddress": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2",
+ "recentBlocks": [],
+ "currentCurrency": "usd",
+ "conversionRate": 225.23,
+ "conversionDate": 1538859376,
+ "shapeShiftTxList": [],
+ "infuraNetworkStatus": {
+ "kovan": "ok",
+ "mainnet": "ok",
+ "rinkeby": "ok",
+ "ropsten": "ok"
+ },
+ "lostAccounts": []
+ },
+ "appState": {
+ "shouldClose": false,
+ "menuOpen": false,
+ "modal": {
+ "open": false,
+ "modalState": {
+ "name": null,
+ "props": {}
+ },
+ "previousModalState": {
+ "name": null
+ }
+ },
+ "sidebar": {
+ "isOpen": false,
+ "transitionName": "",
+ "type": ""
+ },
+ "alertOpen": false,
+ "alertMessage": null,
+ "qrCodeData": null,
+ "networkDropdownOpen": false,
+ "currentView": {
+ "name": "confTx",
+ "context": 0
+ },
+ "accountDetail": {
+ "subview": "transactions"
+ },
+ "transForward": false,
+ "isLoading": false,
+ "warning": null,
+ "buyView": {},
+ "isMouseUser": true,
+ "gasIsLoading": false,
+ "networkNonce": "0x92",
+ "defaultHdPaths": {
+ "trezor": "m/44'/60'/0'/0",
+ "ledger": "m/44'/60'/0'/0/0"
+ }
+ },
+ "localeMessages": {},
+ "send": {
+ "fromDropdownOpen": false,
+ "toDropdownOpen": false,
+ "errors": {}
+ },
+ "confirmTransaction": {
+ "txData": {
+ "estimatedGas": "0x38f53",
+ "gasLimitSpecified": true,
+ "gasPriceSpecified": false,
+ "history": [],
+ "id": 2389644572638774,
+ "loadingDefaults": false,
+ "metamaskNetworkId": "4",
+ "origin": "remix.ethereum.org",
+ "status": "unapproved",
+ "time": 1538844223352,
+ "txParams": {
+ "data": "0x608060405234801561001057600080fd5b506102a7806100206000396000f30060806040526004361061004b5763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663d13319c48114610050578063dfb29935146100da575b600080fd5b34801561005c57600080fd5b50610065610135565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561009f578181015183820152602001610087565b50505050905090810190601f1680156100cc5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156100e657600080fd5b506040805160206004803580820135601f81018490048402850184019095528484526101339436949293602493928401919081908401838280828437509497506101cc9650505050505050565b005b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156101c15780601f10610196576101008083540402835291602001916101c1565b820191906000526020600020905b8154815290600101906020018083116101a457829003601f168201915b505050505090505b90565b80516101df9060009060208401906101e3565b5050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061022457805160ff1916838001178555610251565b82800160010185558215610251579182015b82811115610251578251825591602001919060010190610236565b5061025d929150610261565b5090565b6101c991905b8082111561025d57600081556001016102675600a165627a7a72305820cf4282c534b8f2faad947d592afa109b907e4e6b2f52335b361b69c24fedb9580029",
+ "from": "0xe2f12a09ba1098312a7d1cad7581ed253ca5f4b2",
+ "gas": "0x38f53",
+ "gasPrice": "0x3b9aca00",
+ "value": "0x0"
+ },
+ "type": "standard"
+ },
+ "tokenData": {},
+ "methodData": {},
+ "tokenProps": {
+ "tokenDecimals": "",
+ "tokenSymbol": ""
+ },
+ "fiatTransactionAmount": "0",
+ "fiatTransactionFee": "0.05",
+ "fiatTransactionTotal": "0.05",
+ "ethTransactionAmount": "0",
+ "ethTransactionFee": "0.000233",
+ "ethTransactionTotal": "0.000233",
+ "hexGasTotal": "0xd42f28057e00",
+ "nonce": "",
+ "toSmartContract": false,
+ "fetchingData": false
+ }
+} \ No newline at end of file
diff --git a/test/e2e/beta/from-import-beta-ui.spec.js b/test/e2e/beta/from-import-beta-ui.spec.js
index d2c3f8958..77a61a73e 100644
--- a/test/e2e/beta/from-import-beta-ui.spec.js
+++ b/test/e2e/beta/from-import-beta-ui.spec.js
@@ -159,6 +159,7 @@ describe('Using MetaMask with an existing account', function () {
it('clicks through the ToS', async () => {
// terms of use
+ await delay(largeDelayMs)
const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled()
assert.equal(canClickThrough, false, 'disabled continue button')
const bottomOfTos = await findElement(driver, By.linkText('Attributions'))
diff --git a/test/e2e/beta/metamask-beta-ui.spec.js b/test/e2e/beta/metamask-beta-ui.spec.js
index 3bfa1eaf2..2b5c8ee18 100644
--- a/test/e2e/beta/metamask-beta-ui.spec.js
+++ b/test/e2e/beta/metamask-beta-ui.spec.js
@@ -484,6 +484,142 @@ describe('MetaMask', function () {
})
})
+ describe('Navigate transactions', () => {
+ it('adds multiple transactions', async () => {
+ await delay(regularDelayMs)
+
+ await waitUntilXWindowHandles(driver, 2)
+ const windowHandles = await driver.getAllWindowHandles()
+ const extension = windowHandles[0]
+ const dapp = windowHandles[1]
+
+ await driver.switchTo().window(dapp)
+ await delay(regularDelayMs)
+
+ const send3eth = await findElement(driver, By.xpath(`//button[contains(text(), 'Send')]`), 10000)
+ await send3eth.click()
+ await delay(regularDelayMs)
+
+ const contractDeployment = await findElement(driver, By.xpath(`//button[contains(text(), 'Deploy Contract')]`), 10000)
+ await contractDeployment.click()
+ await delay(regularDelayMs)
+
+ await send3eth.click()
+ await contractDeployment.click()
+ await delay(regularDelayMs)
+
+ await driver.switchTo().window(extension)
+ await delay(regularDelayMs)
+
+ const transactions = await findElements(driver, By.css('.transaction-list-item'))
+ await transactions[3].click()
+ await delay(regularDelayMs)
+ })
+
+ it('navigates the transactions', async () => {
+ let navigateTxButtons = await findElements(driver, By.css('.confirm-page-container-navigation__arrow'))
+ assert.equal(navigateTxButtons.length, 4, 'navigation button present')
+
+ await navigateTxButtons[2].click()
+ let navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation'))
+ let navigationText = await navigationElement.getText()
+ assert.equal(navigationText.includes('2'), true, 'changed transaction right')
+
+ navigateTxButtons = await findElements(driver, By.css('.confirm-page-container-navigation__arrow'))
+ await navigateTxButtons[2].click()
+ navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation'))
+ navigationText = await navigationElement.getText()
+ assert.equal(navigationText.includes('3'), true, 'changed transaction right')
+
+ navigateTxButtons = await findElements(driver, By.css('.confirm-page-container-navigation__arrow'))
+ await navigateTxButtons[2].click()
+ navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation'))
+ navigationText = await navigationElement.getText()
+ assert.equal(navigationText.includes('4'), true, 'changed transaction right')
+
+ navigateTxButtons = await findElements(driver, By.css('.confirm-page-container-navigation__arrow'))
+ await navigateTxButtons[0].click()
+ navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation'))
+ navigationText = await navigationElement.getText()
+ assert.equal(navigationText.includes('1'), true, 'navigate to first transaction')
+
+ navigateTxButtons = await findElements(driver, By.css('.confirm-page-container-navigation__arrow'))
+ await navigateTxButtons[3].click()
+ navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation'))
+ navigationText = await navigationElement.getText()
+ assert.equal(navigationText.split('4').length, 3, 'navigate to last transaction')
+
+ navigateTxButtons = await findElements(driver, By.css('.confirm-page-container-navigation__arrow'))
+ await navigateTxButtons[1].click()
+ navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation'))
+ navigationText = await navigationElement.getText()
+ assert.equal(navigationText.includes('3'), true, 'changed transaction left')
+
+ navigateTxButtons = await findElements(driver, By.css('.confirm-page-container-navigation__arrow'))
+ await navigateTxButtons[1].click()
+ navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation'))
+ navigationText = await navigationElement.getText()
+ assert.equal(navigationText.includes('2'), true, 'changed transaction left')
+ })
+
+ it('adds a transaction while confirm screen is in focus', async () => {
+ let navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation'))
+ let navigationText = await navigationElement.getText()
+ assert.equal(navigationText.includes('2'), true, 'second transaction in focus')
+
+ const windowHandles = await driver.getAllWindowHandles()
+ const extension = windowHandles[0]
+ const dapp = windowHandles[1]
+
+ await driver.switchTo().window(dapp)
+ await delay(regularDelayMs)
+
+ const send3eth = await findElement(driver, By.xpath(`//button[contains(text(), 'Send')]`), 10000)
+ await send3eth.click()
+ await delay(regularDelayMs)
+
+ await driver.switchTo().window(extension)
+ await delay(regularDelayMs)
+
+ navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation'))
+ navigationText = await navigationElement.getText()
+ assert.equal(navigationText.includes('3'), true, 'correct transaction in focus')
+ })
+
+ it('confirms a transaction', async () => {
+ const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`), 10000)
+ await confirmButton.click()
+ await delay(regularDelayMs)
+
+ const navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation'))
+ const navigationText = await navigationElement.getText()
+ assert.equal(navigationText.includes('4'), true, 'transaction confirmed')
+ })
+
+ it('rejects a transaction', async () => {
+ const rejectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Reject')]`), 10000)
+ await rejectButton.click()
+ await delay(regularDelayMs)
+
+ const navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation'))
+ const navigationText = await navigationElement.getText()
+ assert.equal(navigationText.includes('3'), true, 'transaction rejected')
+ })
+
+ it('rejects the rest of the transactions', async () => {
+ const rejectAllButton = await findElement(driver, By.xpath(`//a[contains(text(), 'Reject 3')]`), 10000)
+ await rejectAllButton.click()
+ await delay(regularDelayMs)
+
+ const rejectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Reject All')]`), 10000)
+ await rejectButton.click()
+ await delay(largeDelayMs * 2)
+
+ const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item'))
+ assert.equal(confirmedTxes.length, 3, '3 transactions present')
+ })
+ })
+
describe('Deploy contract and call contract methods', () => {
let extension
let dapp
@@ -531,7 +667,7 @@ describe('MetaMask', function () {
driver.wait(async () => {
const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item'))
- return confirmedTxes.length === 3
+ return confirmedTxes.length === 4
}, 10000)
const txAction = await findElements(driver, By.css('.transaction-list-item__action'))
@@ -588,7 +724,7 @@ describe('MetaMask', function () {
driver.wait(async () => {
const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item'))
- return confirmedTxes.length === 4
+ return confirmedTxes.length === 5
}, 10000)
const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
@@ -620,7 +756,7 @@ describe('MetaMask', function () {
driver.wait(async () => {
const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item'))
- return confirmedTxes.length === 5
+ return confirmedTxes.length === 6
}, 10000)
const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary'))
@@ -634,9 +770,9 @@ describe('MetaMask', function () {
const balance = await findElement(driver, By.css('.transaction-view-balance__primary-balance'))
await delay(regularDelayMs)
if (process.env.SELENIUM_BROWSER !== 'firefox') {
- await driver.wait(until.elementTextMatches(balance, /^92.*\s*ETH.*$/), 10000)
+ await driver.wait(until.elementTextMatches(balance, /^89.*\s*ETH.*$/), 10000)
const tokenAmount = await balance.getText()
- assert.ok(/^92.*\s*ETH.*$/.test(tokenAmount))
+ assert.ok(/^89.*\s*ETH.*$/.test(tokenAmount))
await delay(regularDelayMs)
}
})
diff --git a/test/integration/lib/navigate-txs.js b/test/integration/lib/navigate-txs.js
new file mode 100644
index 000000000..0679d6b00
--- /dev/null
+++ b/test/integration/lib/navigate-txs.js
@@ -0,0 +1,87 @@
+const reactTriggerChange = require('react-trigger-change')
+const {
+ timeout,
+ queryAsync,
+} = require('../../lib/util')
+
+QUnit.module('navigate txs')
+
+QUnit.test('successful navigate', (assert) => {
+ const done = assert.async()
+ runNavigateTxsFlowTest(assert)
+ .then(done)
+ .catch(err => {
+ assert.notOk(err, `Error was thrown: ${err.stack}`)
+ done()
+ })
+})
+
+async function runNavigateTxsFlowTest (assert, done) {
+ const selectState = await queryAsync($, 'select')
+
+ selectState.val('navigate txs')
+ reactTriggerChange(selectState[0])
+
+ // Confirm navigation buttons present
+ let navigateTxButtons = await queryAsync($, '.confirm-page-container-navigation__arrow')
+ assert.ok(navigateTxButtons[0], 'navigation button present')
+ assert.ok(navigateTxButtons[1], 'navigation button present')
+ assert.ok(navigateTxButtons[2], 'navigation button present')
+ assert.ok(navigateTxButtons[3], 'navigation button present')
+
+ // Verify number of transactions present
+ let trxNum = await queryAsync($, '.confirm-page-container-navigation')
+ assert.equal(trxNum[0].innerText.includes('1'), true, 'starts on first')
+
+ // Verify correct route
+ let summaryAction = await queryAsync($, '.confirm-page-container-summary__action')
+ assert.equal(summaryAction[0].innerText, 'CONTRACT DEPLOYMENT', 'correct route')
+
+ // Click navigation button
+ navigateTxButtons[2].click()
+ await timeout(2000)
+
+ // Verify transaction changed to num 2 and routed correctly
+ trxNum = await queryAsync($, '.confirm-page-container-navigation')
+ assert.equal(trxNum[0].innerText.includes('2'), true, 'changed transaction right')
+ summaryAction = await queryAsync($, '.confirm-page-container-summary__action')
+ // assert.equal(summaryAction[0].innerText, 'CONFIRM', 'correct route')
+
+ // Click navigation button
+ navigateTxButtons = await queryAsync($, '.confirm-page-container-navigation__arrow')
+ navigateTxButtons[2].click()
+
+ // Verify transation changed to num 3 and routed correctly
+ trxNum = await queryAsync($, '.confirm-page-container-navigation')
+ assert.equal(trxNum[0].innerText.includes('3'), true, 'changed transaction right')
+ summaryAction = await queryAsync($, '.confirm-page-container-summary__action')
+ assert.equal(summaryAction[0].innerText, 'CONFIRM', 'correct route')
+
+ // Click navigation button
+ navigateTxButtons = await queryAsync($, '.confirm-page-container-navigation__arrow')
+ navigateTxButtons[2].click()
+
+ // Verify transation changed to num 4 and routed correctly
+ trxNum = await queryAsync($, '.confirm-page-container-navigation')
+ assert.equal(trxNum[0].innerText.split('4').length, 3, '4 transactions present')
+ summaryAction = await queryAsync($, '.confirm-page-container-summary__action')
+ assert.equal(summaryAction[0].innerText, 'TRANSFER', 'correct route')
+
+ // Verify left arrow is working correctly
+ navigateTxButtons = await queryAsync($, '.confirm-page-container-navigation__arrow')
+ navigateTxButtons[1].click()
+ trxNum = await queryAsync($, '.confirm-page-container-navigation')
+ assert.equal(trxNum[0].innerText.includes('3'), true, 'changed transaction left')
+
+ // Verify navigate to last transaction is working correctly
+ navigateTxButtons = await queryAsync($, '.confirm-page-container-navigation__arrow')
+ navigateTxButtons[3].click()
+ trxNum = await queryAsync($, '.confirm-page-container-navigation')
+ assert.equal(trxNum[0].innerText.split('4').length, 3, 'navigate to last transaction')
+
+ // Verify navigate to first transaction is working correctly
+ navigateTxButtons = await queryAsync($, '.confirm-page-container-navigation__arrow')
+ navigateTxButtons[0].click()
+ trxNum = await queryAsync($, '.confirm-page-container-navigation')
+ assert.equal(trxNum[0].innerText.includes('1'), true, 'navigate to first transaction')
+}
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-header/index.scss b/ui/app/components/confirm-page-container/confirm-page-container-header/index.scss
index 43e1e4427..be77edbdf 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container-header/index.scss
+++ b/ui/app/components/confirm-page-container/confirm-page-container-header/index.scss
@@ -7,7 +7,7 @@
display: flex;
justify-content: space-between;
border-bottom: 1px solid $geyser;
- padding: 13px 13px 13px 24px;
+ padding: 4px 13px 4px 13px;
flex: 0 0 auto;
}
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js b/ui/app/components/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js
new file mode 100755
index 000000000..8327f997b
--- /dev/null
+++ b/ui/app/components/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js
@@ -0,0 +1,69 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+const ConfirmPageContainerNavigation = props => {
+ const { onNextTx, totalTx, positionOfCurrentTx, nextTxId, prevTxId, showNavigation, firstTx, lastTx, ofText, requestsWaitingText } = props
+
+ return (
+ <div className="confirm-page-container-navigation"
+ style={{
+ display: showNavigation ? 'flex' : 'none',
+ }}
+ >
+ <div className="confirm-page-container-navigation__container"
+ style={{
+ visibility: prevTxId ? 'initial' : 'hidden',
+ }}>
+ <div
+ className="confirm-page-container-navigation__arrow"
+ onClick={() => onNextTx(firstTx)}>
+ <img src="/images/double-arrow.svg" />
+ </div>
+ <div
+ className="confirm-page-container-navigation__arrow"
+ onClick={() => onNextTx(prevTxId)}>
+ <img src="/images/single-arrow.svg" />
+ </div>
+ </div>
+ <div className="confirm-page-container-navigation__textcontainer">
+ <div className="confirm-page-container-navigation__navtext">
+ {positionOfCurrentTx} {ofText} {totalTx}
+ </div>
+ <div className="confirm-page-container-navigation__longtext">
+ {requestsWaitingText}
+ </div>
+ </div>
+ <div
+ className="confirm-page-container-navigation__container"
+ style={{
+ visibility: nextTxId ? 'initial' : 'hidden',
+ }}>
+ <div
+ className="confirm-page-container-navigation__arrow"
+ onClick={() => onNextTx(nextTxId)}>
+ <img className="confirm-page-container-navigation__imageflip" src="/images/single-arrow.svg" />
+ </div>
+ <div
+ className="confirm-page-container-navigation__arrow"
+ onClick={() => onNextTx(lastTx)}>
+ <img className="confirm-page-container-navigation__imageflip" src="/images/double-arrow.svg" />
+ </div>
+ </div>
+ </div>
+ )
+}
+
+ConfirmPageContainerNavigation.propTypes = {
+ totalTx: PropTypes.number,
+ positionOfCurrentTx: PropTypes.number,
+ onNextTx: PropTypes.func,
+ nextTxId: PropTypes.string,
+ prevTxId: PropTypes.string,
+ showNavigation: PropTypes.bool,
+ firstTx: PropTypes.string,
+ lastTx: PropTypes.string,
+ ofText: PropTypes.string,
+ requestsWaitingText: PropTypes.string,
+}
+
+export default ConfirmPageContainerNavigation
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-navigation/index.js b/ui/app/components/confirm-page-container/confirm-page-container-navigation/index.js
new file mode 100755
index 000000000..d97c1b447
--- /dev/null
+++ b/ui/app/components/confirm-page-container/confirm-page-container-navigation/index.js
@@ -0,0 +1 @@
+export { default } from './confirm-page-container-navigation.component'
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-navigation/index.scss b/ui/app/components/confirm-page-container/confirm-page-container-navigation/index.scss
new file mode 100755
index 000000000..0cf184c60
--- /dev/null
+++ b/ui/app/components/confirm-page-container/confirm-page-container-navigation/index.scss
@@ -0,0 +1,54 @@
+.confirm-page-container-navigation {
+ display: flex;
+ justify-content: space-between;
+ font: inherit;
+ padding: 4px 10px 4px 10px;
+ border-bottom: 1px solid $geyser;
+ flex: 0 0 auto;
+
+ &__container {
+ display: flex;
+ }
+
+ &__arrow {
+ cursor: pointer;
+ display: flex;
+ padding-left: 5px;
+ padding-right: 5px;
+ }
+
+ &__arrow:hover {
+ -webkit-transform: scale(1.1);
+ -moz-transform: scale(1.1);
+ -o-transform: scale(1.1);
+ transform: scale(1.1);
+ }
+
+ &__arrow:active {
+ -webkit-transform: scale(0.95);
+ -moz-transform: scale(0.95);
+ -o-transform: scale(0.95);
+ transform: scale(0.95);
+ }
+
+ &__textcontainer {
+ text-align: center;
+ }
+
+ &__navtext {
+ font-size: 9px;
+ font-weight: bold;
+ }
+
+ &__longtext {
+ color: $oslo-gray;
+ font-size: 8px;
+ }
+
+ &__imageflip {
+ -webkit-transform: scaleX(-1);
+ -moz-transform: scaleX(-1);
+ -o-transform: scaleX(-1);
+ transform: scaleX(-1);
+ }
+} \ No newline at end of file
diff --git a/ui/app/components/confirm-page-container/confirm-page-container.component.js b/ui/app/components/confirm-page-container/confirm-page-container.component.js
index 8b2e47cbb..10edf3b16 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container.component.js
+++ b/ui/app/components/confirm-page-container/confirm-page-container.component.js
@@ -2,7 +2,7 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import SenderToRecipient from '../sender-to-recipient'
import { PageContainerFooter } from '../page-container'
-import { ConfirmPageContainerHeader, ConfirmPageContainerContent } from './'
+import { ConfirmPageContainerHeader, ConfirmPageContainerContent, ConfirmPageContainerNavigation } from './'
export default class ConfirmPageContainer extends Component {
static contextTypes = {
@@ -43,6 +43,17 @@ export default class ConfirmPageContainer extends Component {
summaryComponent: PropTypes.node,
warning: PropTypes.string,
unapprovedTxCount: PropTypes.number,
+ // Navigation
+ totalTx: PropTypes.number,
+ positionOfCurrentTx: PropTypes.number,
+ nextTxId: PropTypes.string,
+ prevTxId: PropTypes.string,
+ showNavigation: PropTypes.bool,
+ onNextTx: PropTypes.func,
+ firstTx: PropTypes.string,
+ lastTx: PropTypes.string,
+ ofText: PropTypes.string,
+ requestsWaitingText: PropTypes.string,
// Footer
onCancelAll: PropTypes.func,
onCancel: PropTypes.func,
@@ -79,11 +90,33 @@ export default class ConfirmPageContainer extends Component {
unapprovedTxCount,
assetImage,
warning,
+ totalTx,
+ positionOfCurrentTx,
+ nextTxId,
+ prevTxId,
+ showNavigation,
+ onNextTx,
+ firstTx,
+ lastTx,
+ ofText,
+ requestsWaitingText,
} = this.props
const renderAssetImage = contentComponent || (!contentComponent && !identiconAddress)
return (
<div className="page-container">
+ <ConfirmPageContainerNavigation
+ totalTx={totalTx}
+ positionOfCurrentTx={positionOfCurrentTx}
+ nextTxId={nextTxId}
+ prevTxId={prevTxId}
+ showNavigation={showNavigation}
+ onNextTx={(txId) => onNextTx(txId)}
+ firstTx={firstTx}
+ lastTx={lastTx}
+ ofText={ofText}
+ requestsWaitingText={requestsWaitingText}
+ />
<ConfirmPageContainerHeader
showEdit={showEdit}
onEdit={() => onEdit()}
diff --git a/ui/app/components/confirm-page-container/index.js b/ui/app/components/confirm-page-container/index.js
index ee88aa5d3..28b17614e 100644
--- a/ui/app/components/confirm-page-container/index.js
+++ b/ui/app/components/confirm-page-container/index.js
@@ -1,6 +1,8 @@
export { default } from './confirm-page-container.component'
export { default as ConfirmPageContainerHeader } from './confirm-page-container-header'
export { default as ConfirmDetailRow } from './confirm-detail-row'
+export { default as ConfirmPageContainerNavigation } from './confirm-page-container-navigation'
+
export {
default as ConfirmPageContainerContent,
ConfirmPageContainerSummary,
diff --git a/ui/app/components/confirm-page-container/index.scss b/ui/app/components/confirm-page-container/index.scss
index af7a5b555..d41cd4423 100644
--- a/ui/app/components/confirm-page-container/index.scss
+++ b/ui/app/components/confirm-page-container/index.scss
@@ -3,3 +3,5 @@
@import './confirm-page-container-header/index';
@import './confirm-detail-row/index';
+
+@import './confirm-page-container-navigation/index'; \ No newline at end of file
diff --git a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js
index 7d01aaffb..3a940a505 100644
--- a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js
+++ b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js
@@ -2,7 +2,7 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ConfirmPageContainer, { ConfirmDetailRow } from '../../confirm-page-container'
import { isBalanceSufficient } from '../../send/send.utils'
-import { DEFAULT_ROUTE } from '../../../routes'
+import { DEFAULT_ROUTE, CONFIRM_TRANSACTION_ROUTE } from '../../../routes'
import {
INSUFFICIENT_FUNDS_ERROR_KEY,
TRANSACTION_ERROR_KEY,
@@ -55,6 +55,7 @@ export default class ConfirmTransactionBase extends Component {
transactionStatus: PropTypes.string,
txData: PropTypes.object,
unapprovedTxCount: PropTypes.number,
+ currentNetworkUnapprovedTxs: PropTypes.object,
// Component props
action: PropTypes.string,
contentComponent: PropTypes.node,
@@ -347,6 +348,32 @@ export default class ConfirmTransactionBase extends Component {
/>
)
}
+
+ handleNextTx (txId) {
+ const { history, clearConfirmTransaction } = this.props
+ if (txId) {
+ clearConfirmTransaction()
+ history.push(`${CONFIRM_TRANSACTION_ROUTE}/${txId}`)
+ }
+ }
+
+ getNavigateTxData () {
+ const { currentNetworkUnapprovedTxs, txData: { id } = {} } = this.props
+ const enumUnapprovedTxs = Object.keys(currentNetworkUnapprovedTxs).reverse()
+ const currentPosition = enumUnapprovedTxs.indexOf(id.toString())
+
+ return {
+ totalTx: enumUnapprovedTxs.length,
+ positionOfCurrentTx: currentPosition + 1,
+ nextTxId: enumUnapprovedTxs[currentPosition + 1],
+ prevTxId: enumUnapprovedTxs[currentPosition - 1],
+ showNavigation: enumUnapprovedTxs.length > 1,
+ firstTx: enumUnapprovedTxs[0],
+ lastTx: enumUnapprovedTxs[enumUnapprovedTxs.length - 1],
+ ofText: this.context.t('ofTextNofM'),
+ requestsWaitingText: this.context.t('requestsAwaitingAcknowledgement'),
+ }
+ }
render () {
const {
@@ -376,6 +403,7 @@ export default class ConfirmTransactionBase extends Component {
const { name } = methodData
const { valid, errorKey } = this.getErrorKey()
+ const { totalTx, positionOfCurrentTx, nextTxId, prevTxId, showNavigation, firstTx, lastTx, ofText, requestsWaitingText } = this.getNavigateTxData()
return (
<ConfirmPageContainer
@@ -401,6 +429,16 @@ export default class ConfirmTransactionBase extends Component {
errorMessage={errorMessage || submitError}
errorKey={propsErrorKey || errorKey}
warning={warning}
+ totalTx={totalTx}
+ positionOfCurrentTx={positionOfCurrentTx}
+ nextTxId={nextTxId}
+ prevTxId={prevTxId}
+ showNavigation={showNavigation}
+ onNextTx={(txId) => this.handleNextTx(txId)}
+ firstTx={firstTx}
+ lastTx={lastTx}
+ ofText={ofText}
+ requestsWaitingText={requestsWaitingText}
disabled={!propsValid || !valid || submitting}
onEdit={() => this.handleEdit()}
onCancelAll={() => this.handleCancelAll()}
diff --git a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js
index c366d5137..45bf62fb9 100644
--- a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js
+++ b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js
@@ -73,9 +73,9 @@ const mapStateToProps = (state, props) => {
const currentNetworkUnapprovedTxs = R.filter(
({ metamaskNetworkId }) => metamaskNetworkId === network,
- valuesFor(unapprovedTxs),
+ unapprovedTxs,
)
- const unapprovedTxCount = currentNetworkUnapprovedTxs.length
+ const unapprovedTxCount = valuesFor(currentNetworkUnapprovedTxs).length
return {
balance,
@@ -104,6 +104,7 @@ const mapStateToProps = (state, props) => {
assetImage,
unapprovedTxs,
unapprovedTxCount,
+ currentNetworkUnapprovedTxs,
}
}
diff --git a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js
index 2c44b6094..76782cf6a 100644
--- a/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js
+++ b/ui/app/components/pages/confirm-transaction-switch/confirm-transaction-switch.component.js
@@ -32,21 +32,15 @@ export default class ConfirmTransactionSwitch extends Component {
txData,
methodData: { name },
fetchingData,
- isEtherTransaction,
} = this.props
const { id, txParams: { data } = {} } = txData
- if (isConfirmDeployContract(txData)) {
- const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_DEPLOY_CONTRACT_PATH}`
- return <Redirect to={{ pathname }} />
- }
-
if (fetchingData) {
return <Loading />
}
- if (isEtherTransaction) {
- const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SEND_ETHER_PATH}`
+ if (isConfirmDeployContract(txData)) {
+ const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_DEPLOY_CONTRACT_PATH}`
return <Redirect to={{ pathname }} />
}
diff --git a/ui/app/ducks/confirm-transaction.duck.js b/ui/app/ducks/confirm-transaction.duck.js
index 275eb1551..e228d2d39 100644
--- a/ui/app/ducks/confirm-transaction.duck.js
+++ b/ui/app/ducks/confirm-transaction.duck.js
@@ -370,11 +370,16 @@ export function setTransactionToConfirm (transactionId) {
dispatch(setFetchingData(true))
const methodData = await getMethodData(data)
dispatch(updateMethodData(methodData))
+ } catch (error) {
+ dispatch(updateMethodData({}))
+ dispatch(setFetchingData(false))
+ }
+
+ try {
const toSmartContract = await isSmartContractAddress(to)
dispatch(updateToSmartContract(toSmartContract))
dispatch(setFetchingData(false))
} catch (error) {
- dispatch(updateMethodData({}))
dispatch(setFetchingData(false))
}