diff --git a/old-ui/app/components/account-dropdowns.js b/old-ui/app/components/account-dropdowns.js index 45e584510..010385adf 100644 --- a/old-ui/app/components/account-dropdowns.js +++ b/old-ui/app/components/account-dropdowns.js @@ -177,20 +177,18 @@ class AccountDropdowns extends Component { } renderAccountOptions () { - const { actions, network } = this.props + const { actions } = this.props const { optionsMenuActive } = this.state - const isSokol = parseInt(network) === 77 - const isPOA = parseInt(network) === 99 - const explorerStr = (isSokol || isPOA) ? 'POA explorer' : 'Etherscan' - return h( Dropdown, { style: { + position: 'relative', marginLeft: '-234px', minWidth: '180px', marginTop: '30px', + top: '30px', width: '280px', }, isOpen: optionsMenuActive, @@ -213,7 +211,7 @@ class AccountDropdowns extends Component { global.platform.openWindow({ url }) }, }, - `View account on ${explorerStr}`, + `View on block explorer`, ), h( DropdownMenuItem, @@ -237,7 +235,7 @@ class AccountDropdowns extends Component { copyToClipboard(checkSumAddress) }, }, - 'Copy Address to clipboard', + 'Copy address to clipboard', ), h( DropdownMenuItem, @@ -283,15 +281,8 @@ class AccountDropdowns extends Component { this.renderAccountSelector(), ), enableAccountOptions && h( - 'div.account-dropdown', + 'div.address-dropdown.account-dropdown', { - style: { - backgroundImage: 'url(../images/more.svg)', - width: '30px', - height: '24px', - backgroundRepeat: 'no-repeat', - backgroundPosition: 'center', - }, onClick: (event) => { event.stopPropagation() this.setState({ diff --git a/old-ui/app/components/token-cell.js b/old-ui/app/components/token-cell.js index cb1d0c7fd..e24000318 100644 --- a/old-ui/app/components/token-cell.js +++ b/old-ui/app/components/token-cell.js @@ -3,23 +3,34 @@ const h = require('react-hyperscript') const inherits = require('util').inherits const Identicon = require('./identicon') const ethNetProps = require('eth-net-props') +const Dropdown = require('./dropdown').Dropdown +const DropdownMenuItem = require('./dropdown').DropdownMenuItem +const ethUtil = require('ethereumjs-util') +const copyToClipboard = require('copy-to-clipboard') + +const tokenCellDropDownPrefix = 'token-cell_dropdown_' module.exports = TokenCell inherits(TokenCell, Component) function TokenCell () { Component.call(this) + + this.state = { + optionsMenuActive: false, + } + this.optionsMenuToggleClassName = 'token-dropdown' } TokenCell.prototype.render = function () { - const props = this.props - const { address, symbol, string, network, userAddress } = props + const { address, symbol, string, network, userAddress, isLastTokenCell, menuToTop, ind } = this.props + const { optionsMenuActive } = this.state return ( - h('li.token-cell', { + h(`li#token-cell_${ind}.token-cell`, { style: { cursor: network === '1' ? 'pointer' : 'default', - borderBottom: props.isLastTokenCell ? 'none' : '1px solid #e2e2e2', + borderBottom: isLastTokenCell ? 'none' : '1px solid #e2e2e2', padding: '20px 0', margin: '0 30px', }, @@ -41,13 +52,18 @@ TokenCell.prototype.render = function () { h('span', { style: { flex: '1 0 auto' } }), - h('span.trash', { - style: { cursor: 'pointer' }, - onClick: (event) => { - event.stopPropagation() - this.props.removeToken({ address, symbol, string, network, userAddress }) + h(`div#${tokenCellDropDownPrefix}${ind}.address-dropdown.token-dropdown`, + { + style: { cursor: 'pointer' }, + onClick: (event) => { + event.stopPropagation() + this.setState({ + optionsMenuActive: !optionsMenuActive, + }) + }, }, - }, ''), + this.renderTokenOptions(menuToTop, ind) + ), /* h('button', { @@ -59,6 +75,68 @@ TokenCell.prototype.render = function () { ) } +TokenCell.prototype.renderTokenOptions = function (menuToTop, ind) { + const { address, symbol, string, network, userAddress } = this.props + const { optionsMenuActive } = this.state + + return h( + Dropdown, + { + style: { + position: 'relative', + marginLeft: '-263px', + minWidth: '180px', + marginTop: menuToTop ? '-200px' : '30px', + width: '280px', + }, + isOpen: optionsMenuActive, + onClickOutside: (event) => { + const { classList, id: targetID } = event.target + const isNotToggleCell = !classList.contains(this.optionsMenuToggleClassName) + const isAnotherCell = targetID !== `${tokenCellDropDownPrefix}${ind}` + if (optionsMenuActive && (isNotToggleCell || (!isNotToggleCell && isAnotherCell))) { + this.setState({ optionsMenuActive: false }) + } + }, + }, + [ + h( + DropdownMenuItem, + { + closeMenu: () => {}, + onClick: () => { + const { network } = this.props + const url = ethNetProps.explorerLinks.getExplorerTokenLinkFor(address, userAddress, network) + global.platform.openWindow({ url }) + }, + }, + `View token on block explorer`, + ), + h( + DropdownMenuItem, + { + closeMenu: () => {}, + onClick: () => { + const checkSumAddress = address && ethUtil.toChecksumAddress(address) + copyToClipboard(checkSumAddress) + }, + }, + 'Copy address to clipboard', + ), + h( + DropdownMenuItem, + { + closeMenu: () => {}, + onClick: () => { + this.props.removeToken({ address, symbol, string, network, userAddress }) + }, + }, + 'Remove', + ), + ] + ) +} + TokenCell.prototype.send = function (address, event) { event.preventDefault() event.stopPropagation() diff --git a/old-ui/app/components/token-list.js b/old-ui/app/components/token-list.js index f1b8f7dd0..9cc95bf1e 100644 --- a/old-ui/app/components/token-list.js +++ b/old-ui/app/components/token-list.js @@ -81,10 +81,13 @@ TokenList.prototype.render = function () { const tokenViews = tokensFromCurrentNetwork.map((tokenData, ind) => { tokenData.userAddress = userAddress - const isLastTokenCell = ind === (tokens.length - 1) + const isLastTokenCell = ind === (tokensFromCurrentNetwork.length - 1) + const menuToTop = true return h(TokenCell, { + ind, ...tokenData, isLastTokenCell, + menuToTop, removeToken: this.props.removeToken, }) }) @@ -94,7 +97,6 @@ TokenList.prototype.render = function () { h('ol.full-flex-height.flex-column', { style: { - overflowY: 'auto', display: 'flex', flexDirection: 'column', }, diff --git a/old-ui/app/css/index.css b/old-ui/app/css/index.css index 6b325cea3..e16be4394 100644 --- a/old-ui/app/css/index.css +++ b/old-ui/app/css/index.css @@ -29,6 +29,14 @@ html { overflow: -moz-scrollbars-none; } +.address-dropdown { + background-image: url(../images/more.svg); + width: 30px; + height: 24px; + background-repeat: no-repeat; + background-position: center; +} + .account-detail-section::-webkit-scrollbar { width: 0; } diff --git a/package-lock.json b/package-lock.json index 3588fbb20..2dffb235f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8754,9 +8754,9 @@ } }, "eth-net-props": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/eth-net-props/-/eth-net-props-1.0.2.tgz", - "integrity": "sha512-lstMFwDb3GU3mUmWi1aGT+JlcK2qf54E+edE5x6uLrJEPr9ZYrFCuTaXyXZU8R1CEHTSaDtwRB7iTyTBVsIzoQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/eth-net-props/-/eth-net-props-1.0.3.tgz", + "integrity": "sha512-iHY/o2/JwEk+HOTFmn5ju1LXsMf+qNsJZ30SdD3WZ9PJH9jc7lqI6fh80xQHWqV7Q2pkZqD5YvO3G6fdrKzQ0Q==", "requires": { "chai": "^4.1.2" } diff --git a/package.json b/package.json index aae6564a5..89722b7e6 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "eth-json-rpc-filters": "^1.2.6", "eth-json-rpc-infura": "^3.0.0", "eth-method-registry": "^1.0.0", - "eth-net-props": "^1.0.2", + "eth-net-props": "^1.0.3", "eth-phishing-detect": "^1.1.4", "eth-query": "^2.1.2", "eth-sig-util": "^1.4.2", diff --git a/test/e2e/elements.js b/test/e2e/elements.js index b7800791c..11c44002d 100644 --- a/test/e2e/elements.js +++ b/test/e2e/elements.js @@ -5,6 +5,16 @@ module.exports = { loader: By.css('#app-content > div > div.full-flex-height > img'), }, menus: { + token: { + menu: By.id('token-cell_dropdown_0'), + items: By.className('dropdown-menu-item'), + view: By.css('#token-cell_dropdown_0 > div > div > li:nth-child(2)'), + copy: By.css('#token-cell_dropdown_0 > div > div > li:nth-child(3)'), + remove: By.css('#token-cell_dropdown_0 > div > div > li:nth-child(4)'), + viewText: 'View token on block explorer', + copyText: 'Copy address to clipboard', + removeText: 'Remove', + }, sandwich: { menu: By.css('.sandwich-expando'), settings: By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(2)'), @@ -187,7 +197,6 @@ module.exports = { balance: By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > div.ether-balance.ether-balance-amount > div > div > div:nth-child(1) > div:nth-child(1)'), address: By.css('#app-content > div > div.app-primary.from-left > div > div > div:nth-child(1) > flex-column > div.flex-row > div'), tokens: { - remove: By.className('trash'), menu: By.className('inactiveForm pointer'), token: By.className('token-cell'), balance: By.css('#app-content > div > div.app-primary.from-left > div > section > div.full-flex-height > ol > li:nth-child(2) > h3'), @@ -209,7 +218,7 @@ module.exports = { title: By.className('page-subtitle'), titleText: 'Remove Token', label: By.className('confirm-label'), - labelText: 'Are you sure you want to remove token', + labelText: 'Are you sure you want to remove token "TST"?', buttons: { back: By.className('fa fa-arrow-left fa-lg cursor-pointer'), no: By.className('btn-violet'), diff --git a/test/e2e/metamask.spec.js b/test/e2e/metamask.spec.js index bae675ad6..b98a0a24b 100644 --- a/test/e2e/metamask.spec.js +++ b/test/e2e/metamask.spec.js @@ -50,7 +50,7 @@ describe('Metamask popup page', async function () { }) after(async function () { - await driver.quit() + // await driver.quit() }) describe('Setup', async function () { @@ -859,22 +859,25 @@ describe('Metamask popup page', async function () { it('remove tokens', async function () { await setProvider(NETWORKS.MAINNET) - + let menu let button let counter let buttonYes - button = await waitUntilShowUp(screens.main.tokens.remove) + menu = await waitUntilShowUp(menus.token.menu) + await menu.click() + button = await waitUntilShowUp(menus.token.remove) await button.click() buttonYes = await waitUntilShowUp(screens.removeToken.buttons.yes) await buttonYes.click() - await delay(500) counter = await waitUntilShowUp(screens.main.tokens.counter) assert.equal(await counter.getText(), 'You own 1 token', 'incorrect value of counter') const tokensNumber = await driver.findElements(screens.main.tokens.token) assert.equal(tokensNumber.length, 1, 'incorrect amount of token\'s is displayed') - button = await waitUntilShowUp(screens.main.tokens.remove) + menu = await waitUntilShowUp(menus.token.menu) + await menu.click() + button = await waitUntilShowUp(menus.token.remove) await button.click() buttonYes = await waitUntilShowUp(screens.removeToken.buttons.yes) await buttonYes.click() @@ -997,6 +1000,14 @@ describe('Metamask popup page', async function () { const tokenBalance = await waitUntilShowUp(screens.main.tokens.balance) assert.equal(await tokenBalance.getText(), '0 TST') }) + + it('click to token opens the etherscan', async function () { + await (await waitUntilShowUp(screens.main.tokens.token)).click() + await switchToLastPage() + const title = await driver.getCurrentUrl() + assert.equal(title.includes('https://etherscan.io/token/'), true, 'link leads to wrong page') + await switchToFirstPage() + }) }) describe('Check support of token per network basis ', async function () { @@ -1081,13 +1092,52 @@ describe('Metamask popup page', async function () { }) }) + describe('Token menu', function () { + + it('token menu is displayed and clickable ', async function () { + const menu = await waitUntilShowUp(menus.token.menu) + await menu.click() + }) + + it('link \'View on blockexplorer...\' leads to correct page ', async function () { + const menu = await waitUntilShowUp(menus.token.view) + assert.notEqual(menu, false, 'item isn\'t displayed') + assert.equal(await menu.getText(), menus.token.viewText, 'incorrect name') + await menu.click() + await switchToLastPage() + const title = await driver.getCurrentUrl() + assert.equal(title.includes('https://etherscan.io/token/'), true, 'link leads to wrong page') + await switchToFirstPage() + }) + + it('item \'Copy\' is displayed and clickable ', async function () { + let menu = await waitUntilShowUp(menus.token.menu) + await menu.click() + const item = await waitUntilShowUp(menus.token.copy) + assert.notEqual(item, false, 'item isn\'t displayed') + assert.equal(await item.getText(), menus.token.copyText, 'incorrect name') + await item.click() + menu = await waitUntilShowUp(menus.token.menu, 10) + assert.notEqual(menu, false, 'menu wasn\'t closed') + }) + + it('item \'Remove\' is displayed', async function () { + const menu = await waitUntilShowUp(menus.token.menu) + await menu.click() + const item = await waitUntilShowUp(menus.token.remove) + assert.notEqual(item, false, 'item isn\'t displayed') + assert.equal(await item.getText(), menus.token.removeText, 'incorrect name') + }) + }) + describe('Remove token , provider is localhost', function () { - it('button \'Remove token\' displayed', async function () { + it('remove option opens \'Remove token\' screen ', async function () { await setProvider(NETWORKS.LOCALHOST) - const removeTokenButton = await waitUntilShowUp(screens.main.tokens.remove) - assert.notEqual(removeTokenButton, false, 'button isn\'t displayed') - await removeTokenButton.click() + const menu = await waitUntilShowUp(menus.token.menu) + await menu.click() + const remove = await waitUntilShowUp(menus.token.remove) + await remove.click() }) it('screen \'Remove token\' has correct title', async function () { @@ -1095,6 +1145,11 @@ describe('Metamask popup page', async function () { assert.equal(await title.getText(), screens.removeToken.titleText, 'title is incorrect') }) + it('screen \'Remove token\' has correct label', async function () { + const title = await waitUntilShowUp(screens.removeToken.label) + assert.equal(await title.getText(), screens.removeToken.labelText, 'label is incorrect') + }) + it('button "No" bring back to "Main" screen', async function () { const title = await waitUntilShowUp(screens.removeToken.title) assert.equal(await title.getText(), screens.removeToken.titleText, 'title is incorrect') @@ -1107,9 +1162,11 @@ describe('Metamask popup page', async function () { }) it('button "Yes" delete token', async function () { - const removeTokenButton = await waitUntilShowUp(screens.main.tokens.remove) - assert.notEqual(removeTokenButton, false, 'button isn\'t displayed') - await removeTokenButton.click() + const menu = await waitUntilShowUp(menus.token.menu) + await menu.click() + const remove = await waitUntilShowUp(menus.token.remove) + await remove.click() + const title = await waitUntilShowUp(screens.removeToken.title) assert.equal(await title.getText(), screens.removeToken.titleText, 'title is incorrect') @@ -1119,6 +1176,36 @@ describe('Metamask popup page', async function () { await click(button) assert.equal(await assertTokensNotDisplayed(), true, 'tokens are displayed') }) + + it('check if token was removed from SOKOL network', async function () { + await setProvider(NETWORKS.SOKOL) + assert.equal(await assertTokensNotDisplayed(), true, 'tokens are displayed') + }) + + it('check if token was removed from KOVAN network', async function () { + await setProvider(NETWORKS.KOVAN) + assert.equal(await assertTokensNotDisplayed(), true, 'tokens are displayed') + }) + + it('check if token was removed from ROPSTEN network', async function () { + await setProvider(NETWORKS.ROPSTEN) + assert.equal(await assertTokensNotDisplayed(), true, 'tokens are displayed') + }) + + it('check if token was removed from MAINNET network', async function () { + await setProvider(NETWORKS.MAINNET) + assert.equal(await assertTokensNotDisplayed(), true, 'tokens are displayed') + }) + + it('check if token was removed from POA network', async function () { + await setProvider(NETWORKS.POA) + assert.equal(await assertTokensNotDisplayed(), true, 'tokens are displayed') + }) + + it('check if token was removed from RINKEBY network', async function () { + await setProvider(NETWORKS.RINKEBY) + assert.equal(await assertTokensNotDisplayed(), true, 'tokens are displayed') + }) }) }) @@ -1170,6 +1257,7 @@ describe('Metamask popup page', async function () { await click(button) await delay(1000) assert.equal(await waitUntilShowUp(screens.settings.buttons.delete, 5), false, 'invalid Rpc was added') + await waitUntilShowUp(screens.settings.error) const errors = await driver.findElements(screens.settings.error) assert.equal(errors.length, 1, 'error isn\'t displayed if Rpc url incorrect') assert.equal(await errors[0].getText(), screens.settings.errors.invalidRpcEndpoint, 'error\'s text incorrect') @@ -1256,6 +1344,7 @@ describe('Metamask popup page', async function () { }) it('deleted custom rpc isn\'t displayed in network dropdown menu', async function () { + await delay(2000) let menu = await waitUntilShowUp(screens.main.network) await menu.click() await waitUntilShowUp(menus.networks.addedCustomRpc, 20) @@ -1442,4 +1531,36 @@ describe('Metamask popup page', async function () { const htmlSource = await driver.getPageSource() await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource) } + + async function switchToLastPage () { + try { + const allHandles = await driver.getAllWindowHandles() + await driver.switchTo().window(allHandles[allHandles.length - 1]) + let counter = 100 + do { + await delay(500) + if (await driver.getCurrentUrl() !== '') return true + } + while (counter-- > 0) + return true + } catch (err) { + return false + } + } + + async function switchToFirstPage () { + try { + const allHandles = await driver.getAllWindowHandles() + await driver.switchTo().window(allHandles[0]) + let counter = 100 + do { + await delay(500) + if (await driver.getCurrentUrl() !== '') return true + } + while (counter-- > 0) + return true + } catch (err) { + return false + } + } })