diff options
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)) } |