Merge pull request #142 from poanetwork/token-menu

(Feature) Token menu: view in explorer/copy/remove
This commit is contained in:
Victor Baranov 2018-10-02 19:38:32 +03:00 committed by GitHub
commit 487a82399c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 225 additions and 45 deletions

View File

@ -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({

View File

@ -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()

View File

@ -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',
},

View File

@ -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;
}

6
package-lock.json generated
View File

@ -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"
}

View File

@ -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",

View File

@ -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'),

View File

@ -50,7 +50,7 @@ describe('Metamask popup page', async function () {
})
after(async function () {
await driver.quit()
// await driver.quit()
})
describe('Setup', async function () {
@ -858,22 +858,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()
@ -975,6 +978,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 () {
@ -1070,13 +1081,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 () {
@ -1084,6 +1134,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')
@ -1096,9 +1151,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')
@ -1109,6 +1166,7 @@ describe('Metamask popup page', async function () {
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')
@ -1189,6 +1247,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')
@ -1275,6 +1334,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)
@ -1453,4 +1513,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
}
}
})