mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 01:47:00 +01:00
Merge branch 'develop' into ci-publish-release
This commit is contained in:
commit
7a65c22a02
@ -54,7 +54,6 @@ workflows:
|
||||
- all-tests-pass:
|
||||
requires:
|
||||
- test-lint
|
||||
- test-deps
|
||||
- test-unit
|
||||
- test-e2e-chrome
|
||||
- test-e2e-firefox
|
||||
@ -416,3 +415,4 @@ jobs:
|
||||
- run:
|
||||
name: All Tests Passed
|
||||
command: echo 'weew - everything passed!'
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
- Fix bug that prevents setting language locale in settings.
|
||||
- Show checksum addresses throughout the UI
|
||||
- Allow transactions with a 0 gwei gas price
|
||||
- Made provider RPC errors contain useful messages
|
||||
|
||||
## 4.5.5 Fri Apr 06 2018
|
||||
|
||||
|
@ -166,7 +166,7 @@ function documentElementCheck () {
|
||||
|
||||
/**
|
||||
* Checks if the current domain is blacklisted
|
||||
*
|
||||
*
|
||||
* @returns {boolean} {@code true} if the current domain is blacklisted
|
||||
*/
|
||||
function blacklistedDomainCheck () {
|
||||
@ -175,6 +175,7 @@ function blacklistedDomainCheck () {
|
||||
'dropbox.com',
|
||||
'webbyawards.com',
|
||||
'cdn.shopify.com/s/javascripts/tricorder/xtld-read-only-frame.html',
|
||||
'adyen.com',
|
||||
]
|
||||
var currentUrl = window.location.href
|
||||
var currentRegex
|
||||
|
@ -13,19 +13,17 @@ class AddressBookController {
|
||||
* @param {object} opts Overrides the defaults for the initial state of this.store
|
||||
* @property {array} opts.initState initializes the the state of the AddressBookController. Can contain an
|
||||
* addressBook property to initialize the addressBook array
|
||||
* @param {KeyringController} keyringController (Soon to be deprecated) The keyringController used in the current
|
||||
* MetamaskController. Contains the identities used in this AddressBookController.
|
||||
* @property {object} opts.preferencesStore the {@code PreferencesController} store
|
||||
* @property {object} store The the store of the current users address book
|
||||
* @property {array} store.addressBook An array of addresses and nicknames. These are set by the user when sending
|
||||
* to a new address.
|
||||
*
|
||||
*/
|
||||
constructor (opts = {}, keyringController) {
|
||||
const initState = extend({
|
||||
constructor ({initState, preferencesStore}) {
|
||||
this.store = new ObservableStore(extend({
|
||||
addressBook: [],
|
||||
}, opts.initState)
|
||||
this.store = new ObservableStore(initState)
|
||||
this.keyringController = keyringController
|
||||
}, initState))
|
||||
this._preferencesStore = preferencesStore
|
||||
}
|
||||
|
||||
//
|
||||
@ -62,7 +60,7 @@ class AddressBookController {
|
||||
*/
|
||||
_addToAddressBook (address, name) {
|
||||
const addressBook = this._getAddressBook()
|
||||
const identities = this._getIdentities()
|
||||
const {identities} = this._preferencesStore.getState()
|
||||
|
||||
const addressBookIndex = addressBook.findIndex((element) => { return element.address.toLowerCase() === address.toLowerCase() || element.name === name })
|
||||
const identitiesIndex = Object.keys(identities).findIndex((element) => { return element.toLowerCase() === address.toLowerCase() })
|
||||
@ -95,19 +93,6 @@ class AddressBookController {
|
||||
_getAddressBook () {
|
||||
return this.store.getState().addressBook
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves identities from the keyring controller in order to avoid
|
||||
* duplication
|
||||
*
|
||||
* @deprecated
|
||||
* @returns {array} Returns the identies array from the keyringContoller's state
|
||||
*
|
||||
*/
|
||||
_getIdentities () {
|
||||
return this.keyringController.memStore.getState().identities
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = AddressBookController
|
||||
|
@ -27,6 +27,7 @@ class PreferencesController {
|
||||
useBlockie: false,
|
||||
featureFlags: {},
|
||||
currentLocale: opts.initLangCode,
|
||||
identities: {},
|
||||
}, opts.initState)
|
||||
this.store = new ObservableStore(initState)
|
||||
}
|
||||
@ -62,6 +63,16 @@ class PreferencesController {
|
||||
this.store.updateState({ currentLocale: key })
|
||||
}
|
||||
|
||||
setAddresses (addresses) {
|
||||
const oldIdentities = this.store.getState().identities
|
||||
const identities = addresses.reduce((ids, address, index) => {
|
||||
const oldId = oldIdentities[address] || {}
|
||||
ids[address] = {name: `Account ${index + 1}`, address, ...oldId}
|
||||
return ids
|
||||
}, {})
|
||||
this.store.updateState({ identities })
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the `selectedAddress` property
|
||||
*
|
||||
@ -155,6 +166,21 @@ class PreferencesController {
|
||||
return this.store.getState().tokens
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a custom label for an account
|
||||
* @param {string} account the account to set a label for
|
||||
* @param {string} label the custom label for the account
|
||||
* @return {Promise<string>}
|
||||
*/
|
||||
setAccountLabel (account, label) {
|
||||
const address = normalizeAddress(account)
|
||||
const {identities} = this.store.getState()
|
||||
identities[address] = identities[address] || {}
|
||||
identities[address].name = label
|
||||
this.store.updateState({ identities })
|
||||
return Promise.resolve(label)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an updated rpc list from this.addToFrequentRpcList() and sets the `frequentRpcList` to this update list.
|
||||
*
|
||||
@ -189,8 +215,8 @@ class PreferencesController {
|
||||
* The returned list will have a max length of 2. If the _url currently exists it the list, it will be moved to the
|
||||
* end of the list. The current list is modified and returned as a promise.
|
||||
*
|
||||
* @param {string} _url The rpc url to add to the frequentRpcList.
|
||||
* @returns {Promise<array>} The updated frequentRpcList.
|
||||
* @param {string} _url The rpc url to add to the frequentRpcList.
|
||||
* @returns {Promise<array>} The updated frequentRpcList.
|
||||
*
|
||||
*/
|
||||
addToFrequentRpcList (_url) {
|
||||
|
66
app/scripts/lib/createErrorMiddleware.js
Normal file
66
app/scripts/lib/createErrorMiddleware.js
Normal file
@ -0,0 +1,66 @@
|
||||
const log = require('loglevel')
|
||||
|
||||
/**
|
||||
* JSON-RPC error object
|
||||
*
|
||||
* @typedef {Object} RpcError
|
||||
* @property {number} code - Indicates the error type that occurred
|
||||
* @property {Object} [data] - Contains additional information about the error
|
||||
* @property {string} [message] - Short description of the error
|
||||
*/
|
||||
|
||||
/**
|
||||
* Middleware configuration object
|
||||
*
|
||||
* @typedef {Object} MiddlewareConfig
|
||||
* @property {boolean} [override] - Use RPC_ERRORS message in place of provider message
|
||||
*/
|
||||
|
||||
/**
|
||||
* Map of standard and non-standard RPC error codes to messages
|
||||
*/
|
||||
const RPC_ERRORS = {
|
||||
1: 'An unauthorized action was attempted.',
|
||||
2: 'A disallowed action was attempted.',
|
||||
3: 'An execution error occurred.',
|
||||
[-32600]: 'The JSON sent is not a valid Request object.',
|
||||
[-32601]: 'The method does not exist / is not available.',
|
||||
[-32602]: 'Invalid method parameter(s).',
|
||||
[-32603]: 'Internal JSON-RPC error.',
|
||||
[-32700]: 'Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.',
|
||||
internal: 'Internal server error.',
|
||||
unknown: 'Unknown JSON-RPC error.',
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies a JSON-RPC error object in-place to add a human-readable message,
|
||||
* optionally overriding any provider-supplied message
|
||||
*
|
||||
* @param {RpcError} error - JSON-RPC error object
|
||||
* @param {boolean} override - Use RPC_ERRORS message in place of provider message
|
||||
*/
|
||||
function sanitizeRPCError (error, override) {
|
||||
if (error.message && !override) { return error }
|
||||
const message = error.code > -31099 && error.code < -32100 ? RPC_ERRORS.internal : RPC_ERRORS[error.code]
|
||||
error.message = message || RPC_ERRORS.unknown
|
||||
}
|
||||
|
||||
/**
|
||||
* json-rpc-engine middleware that both logs standard and non-standard error
|
||||
* messages and ends middleware stack traversal if an error is encountered
|
||||
*
|
||||
* @param {MiddlewareConfig} [config={override:true}] - Middleware configuration
|
||||
* @returns {Function} json-rpc-engine middleware function
|
||||
*/
|
||||
function createErrorMiddleware ({ override = true } = {}) {
|
||||
return (req, res, next) => {
|
||||
next(done => {
|
||||
const { error } = res
|
||||
if (!error) { return done() }
|
||||
sanitizeRPCError(error)
|
||||
log.error(`MetaMask - RPC Error: ${error.message}`, error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = createErrorMiddleware
|
@ -1,5 +1,6 @@
|
||||
const pump = require('pump')
|
||||
const RpcEngine = require('json-rpc-engine')
|
||||
const createErrorMiddleware = require('./createErrorMiddleware')
|
||||
const createIdRemapMiddleware = require('json-rpc-engine/src/idRemapMiddleware')
|
||||
const createStreamMiddleware = require('json-rpc-middleware-stream')
|
||||
const LocalStorageStore = require('obs-store')
|
||||
@ -44,6 +45,7 @@ function MetamaskInpageProvider (connectionStream) {
|
||||
// handle sendAsync requests via dapp-side rpc engine
|
||||
const rpcEngine = new RpcEngine()
|
||||
rpcEngine.push(createIdRemapMiddleware())
|
||||
rpcEngine.push(createErrorMiddleware())
|
||||
rpcEngine.push(streamMiddleware)
|
||||
self.rpcEngine = rpcEngine
|
||||
}
|
||||
|
@ -144,7 +144,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
// address book controller
|
||||
this.addressBookController = new AddressBookController({
|
||||
initState: initState.AddressBookController,
|
||||
}, this.keyringController)
|
||||
preferencesStore: this.preferencesController.store,
|
||||
})
|
||||
|
||||
// tx mgmt
|
||||
this.txController = new TransactionController({
|
||||
@ -363,6 +364,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
addToken: nodeify(preferencesController.addToken, preferencesController),
|
||||
removeToken: nodeify(preferencesController.removeToken, preferencesController),
|
||||
setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController),
|
||||
setAccountLabel: nodeify(preferencesController.setAccountLabel, preferencesController),
|
||||
setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController),
|
||||
|
||||
// AddressController
|
||||
@ -373,7 +375,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
createNewVaultAndKeychain: nodeify(this.createNewVaultAndKeychain, this),
|
||||
createNewVaultAndRestore: nodeify(this.createNewVaultAndRestore, this),
|
||||
addNewKeyring: nodeify(keyringController.addNewKeyring, keyringController),
|
||||
saveAccountLabel: nodeify(keyringController.saveAccountLabel, keyringController),
|
||||
exportAccount: nodeify(keyringController.exportAccount, keyringController),
|
||||
|
||||
// txController
|
||||
@ -433,7 +434,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
} else {
|
||||
vault = await this.keyringController.createNewVaultAndKeychain(password)
|
||||
this.selectFirstIdentity(vault)
|
||||
const accounts = await this.keyringController.getAccounts()
|
||||
this.preferencesController.setAddresses(accounts)
|
||||
this.selectFirstIdentity()
|
||||
}
|
||||
release()
|
||||
} catch (err) {
|
||||
@ -453,7 +456,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
const release = await this.createVaultMutex.acquire()
|
||||
try {
|
||||
const vault = await this.keyringController.createNewVaultAndRestore(password, seed)
|
||||
this.selectFirstIdentity(vault)
|
||||
const accounts = await this.keyringController.getAccounts()
|
||||
this.preferencesController.setAddresses(accounts)
|
||||
this.selectFirstIdentity()
|
||||
release()
|
||||
return vault
|
||||
} catch (err) {
|
||||
@ -471,12 +476,10 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
*/
|
||||
|
||||
/**
|
||||
* Retrieves the first Identiy from the passed Vault and selects the related address
|
||||
*
|
||||
* @param {} vault
|
||||
* Sets the first address in the state to the selected address
|
||||
*/
|
||||
selectFirstIdentity (vault) {
|
||||
const { identities } = vault
|
||||
selectFirstIdentity () {
|
||||
const { identities } = this.preferencesController.store.getState()
|
||||
const address = Object.keys(identities)[0]
|
||||
this.preferencesController.setSelectedAddress(address)
|
||||
}
|
||||
@ -502,13 +505,15 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
await this.verifySeedPhrase()
|
||||
|
||||
this.preferencesController.setAddresses(newAccounts)
|
||||
newAccounts.forEach((address) => {
|
||||
if (!oldAccounts.includes(address)) {
|
||||
this.preferencesController.setSelectedAddress(address)
|
||||
}
|
||||
})
|
||||
|
||||
return keyState
|
||||
const {identities} = this.preferencesController.store.getState()
|
||||
return {...keyState, identities}
|
||||
}
|
||||
|
||||
/**
|
||||
|
47
app/scripts/migrations/026.js
Normal file
47
app/scripts/migrations/026.js
Normal file
@ -0,0 +1,47 @@
|
||||
const version = 26
|
||||
|
||||
/*
|
||||
|
||||
This migration moves the identities stored in the KeyringController
|
||||
into the PreferencesController
|
||||
|
||||
*/
|
||||
|
||||
const clone = require('clone')
|
||||
|
||||
module.exports = {
|
||||
version,
|
||||
migrate (originalVersionedData) {
|
||||
const versionedData = clone(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
const state = versionedData.data
|
||||
versionedData.data = transformState(state)
|
||||
} catch (err) {
|
||||
console.warn(`MetaMask Migration #${version}` + err.stack)
|
||||
return Promise.reject(err)
|
||||
}
|
||||
return Promise.resolve(versionedData)
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
if (!state.KeyringController || !state.PreferencesController) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!state.KeyringController.walletNicknames) {
|
||||
return state
|
||||
}
|
||||
|
||||
state.PreferencesController.identities = Object.keys(state.KeyringController.walletNicknames)
|
||||
.reduce((identities, address) => {
|
||||
identities[address] = {
|
||||
name: state.KeyringController.walletNicknames[address],
|
||||
address,
|
||||
}
|
||||
return identities
|
||||
}, {})
|
||||
delete state.KeyringController.walletNicknames
|
||||
return state
|
||||
}
|
@ -36,4 +36,5 @@ module.exports = [
|
||||
require('./023'),
|
||||
require('./024'),
|
||||
require('./025'),
|
||||
require('./026'),
|
||||
]
|
||||
|
@ -1,6 +1,16 @@
|
||||
# Guide to Porting MetaMask to a New Environment
|
||||
|
||||
MetaMask has been under continuous development for nearly two years now, and we’ve gradually discovered some very useful abstractions, that have allowed us to grow more easily. A couple of those layers together allow MetaMask to be ported to new environments and contexts increasingly easily.
|
||||
MetaMask has been under continuous development for nearly two years now, and we’ve gradually discovered some useful abstractions that have allowed us to grow more easily. A couple of those layers together allow MetaMask to be ported to new environments and contexts increasingly easily (although it still could be easier, and please let us know if you get stuck!)
|
||||
|
||||
Before we get started, it's worth becoming familiar with our basic architecture:
|
||||
|
||||
![metamask-architecture-diagram](./architecture.png)
|
||||
|
||||
The `metamask-background` describes the file at `app/scripts/background.js`, which is the web extension singleton. This context instantiates an instance of the `MetaMask Controller`, which represents the user's accounts, a connection to the blockchain, and the interaction with new Dapps.
|
||||
|
||||
When a new site is visited, the WebExtension creates a new `ContentScript` in that page's context, which can be seen at `app/scripts/contentscript.js`. This script represents a per-page setup process, which creates the per-page `web3` api, connects it to the background script via the Port API (wrapped in a [stream abstraction](https://github.com/substack/stream-handbook)), and injected into the DOM before anything loads.
|
||||
|
||||
The most confusing part about porting MetaMask to a new platform is the way we provide the Web3 API over a series of streams between contexts. Once you understand how we create the [InpageProvider](../app/scripts/lib/inpage-provider.js) in the [inpage.js script](../app/scripts/inpage.js), you will be able to understand how the [port-stream](../app/scripts/lib/port-stream.js) is just a thin wrapper around the [postMessage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage), and a similar stream API can be wrapped around any communication channel to communicate with the `MetaMaskController` via its `setupUntrustedCommunication(stream, domain)` method.
|
||||
|
||||
### The MetaMask Controller
|
||||
|
||||
@ -90,3 +100,4 @@ If streams seem new and confusing to you, that's ok, they can seem strange at fi
|
||||
## Conclusion
|
||||
|
||||
I hope this has been helpful to you! If you have any other questionsm, or points you think need clarification in this guide, please [open an issue on our GitHub](https://github.com/MetaMask/metamask-plugin/issues/new)!
|
||||
|
||||
|
@ -91,7 +91,7 @@ AccountDetailScreen.prototype.render = function () {
|
||||
isEditingLabel: false,
|
||||
},
|
||||
saveText: (text) => {
|
||||
props.dispatch(actions.saveAccountLabel(selected, text))
|
||||
props.dispatch(actions.setAccountLabel(selected, text))
|
||||
},
|
||||
}, [
|
||||
|
||||
|
1563
package-lock.json
generated
1563
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -15,8 +15,8 @@
|
||||
"test:integration:build": "gulp build:scss",
|
||||
"test:e2e:chrome": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:e2e:run:chrome'",
|
||||
"test:e2e:firefox": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:e2e:run:firefox'",
|
||||
"test:e2e:run:chrome": "SELENIUM_BROWSER=chrome mocha test/e2e/chrome/metamask.spec --bail --recursive",
|
||||
"test:e2e:run:firefox": "SELENIUM_BROWSER=firefox mocha test/e2e/firefox/metamask.spec --bail --recursive",
|
||||
"test:e2e:run:chrome": "SELENIUM_BROWSER=chrome mocha test/e2e/metamask.spec --bail --recursive",
|
||||
"test:e2e:run:firefox": "SELENIUM_BROWSER=firefox mocha test/e2e/metamask.spec --bail --recursive",
|
||||
"test:screens": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:screens:run'",
|
||||
"test:screens:run": "node test/screens/new-ui.js",
|
||||
"test:coverage": "nyc npm run test:unit && npm run test:coveralls-upload",
|
||||
@ -95,7 +95,7 @@
|
||||
"eth-hd-keyring": "^1.2.1",
|
||||
"eth-json-rpc-filters": "^1.2.6",
|
||||
"eth-json-rpc-infura": "^3.0.0",
|
||||
"eth-keyring-controller": "^2.2.0",
|
||||
"eth-keyring-controller": "^3.1.1",
|
||||
"eth-phishing-detect": "^1.1.4",
|
||||
"eth-query": "^2.1.2",
|
||||
"eth-sig-util": "^1.4.2",
|
||||
@ -111,7 +111,7 @@
|
||||
"ethjs-query": "^0.3.4",
|
||||
"express": "^4.15.5",
|
||||
"extension-link-enabler": "^1.0.0",
|
||||
"extensionizer": "^1.0.0",
|
||||
"extensionizer": "^1.0.1",
|
||||
"fast-json-patch": "^2.0.4",
|
||||
"fast-levenshtein": "^2.0.6",
|
||||
"file-loader": "^1.1.11",
|
||||
|
@ -1,314 +0,0 @@
|
||||
const fs = require('fs')
|
||||
const mkdirp = require('mkdirp')
|
||||
const path = require('path')
|
||||
const assert = require('assert')
|
||||
const pify = require('pify')
|
||||
const webdriver = require('selenium-webdriver')
|
||||
const until = require('selenium-webdriver/lib/until')
|
||||
const By = webdriver.By
|
||||
const { delay, buildChromeWebDriver } = require('../func')
|
||||
|
||||
describe('Metamask popup page', function () {
|
||||
let driver, accountAddress, tokenAddress, extensionId
|
||||
|
||||
this.timeout(0)
|
||||
|
||||
before(async function () {
|
||||
const extPath = path.resolve('dist/chrome')
|
||||
driver = buildChromeWebDriver(extPath)
|
||||
await driver.get('chrome://extensions')
|
||||
await delay(500)
|
||||
})
|
||||
|
||||
afterEach(async function () {
|
||||
if (this.currentTest.state === 'failed') {
|
||||
await verboseReportOnFailure(this.currentTest)
|
||||
}
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
await driver.quit()
|
||||
})
|
||||
|
||||
describe('Setup', function () {
|
||||
|
||||
it('switches to Chrome extensions list', async function () {
|
||||
const tabs = await driver.getAllWindowHandles()
|
||||
await driver.switchTo().window(tabs[0])
|
||||
await delay(300)
|
||||
})
|
||||
|
||||
it(`selects MetaMask's extension id and opens it in the current tab`, async function () {
|
||||
extensionId = await getExtensionId()
|
||||
await driver.get(`chrome-extension://${extensionId}/popup.html`)
|
||||
await delay(500)
|
||||
})
|
||||
|
||||
it('sets provider type to localhost', async function () {
|
||||
await driver.wait(until.elementLocated(By.css('#app-content')), 300)
|
||||
await setProviderType('localhost')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Account Creation', () => {
|
||||
|
||||
it('matches MetaMask title', async () => {
|
||||
const title = await driver.getTitle()
|
||||
assert.equal(title, 'MetaMask', 'title matches MetaMask')
|
||||
})
|
||||
|
||||
it('shows privacy notice', async () => {
|
||||
await driver.wait(async () => {
|
||||
const privacyHeader = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > h3')).getText()
|
||||
assert.equal(privacyHeader, 'PRIVACY NOTICE', 'shows privacy notice')
|
||||
return privacyHeader === 'PRIVACY NOTICE'
|
||||
}, 300)
|
||||
await driver.findElement(By.css('button')).click()
|
||||
})
|
||||
|
||||
it('show terms of use', async () => {
|
||||
await driver.wait(async () => {
|
||||
const terms = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > h3')).getText()
|
||||
assert.equal(terms, 'TERMS OF USE', 'shows terms of use')
|
||||
return terms === 'TERMS OF USE'
|
||||
})
|
||||
})
|
||||
|
||||
it('checks if the TOU button is disabled', async () => {
|
||||
const button = await driver.findElement(By.css('button')).isEnabled()
|
||||
assert.equal(button, false, 'disabled continue button')
|
||||
const element = await driver.findElement(By.linkText('Attributions'))
|
||||
await driver.executeScript('arguments[0].scrollIntoView(true)', element)
|
||||
await delay(300)
|
||||
})
|
||||
|
||||
it('allows the button to be clicked when scrolled to the bottom of TOU', async () => {
|
||||
const button = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > button'))
|
||||
const buttonEnabled = await button.isEnabled()
|
||||
assert.equal(buttonEnabled, true, 'enabled continue button')
|
||||
await button.click()
|
||||
})
|
||||
|
||||
it('accepts password with length of eight', async () => {
|
||||
const passwordBox = await driver.findElement(By.id('password-box'))
|
||||
const passwordBoxConfirm = await driver.findElement(By.id('password-box-confirm'))
|
||||
const button = await driver.findElements(By.css('button'))
|
||||
|
||||
await passwordBox.sendKeys('123456789')
|
||||
await passwordBoxConfirm.sendKeys('123456789')
|
||||
await button[0].click()
|
||||
await delay(500)
|
||||
})
|
||||
|
||||
it('shows value was created and seed phrase', async () => {
|
||||
await delay(300)
|
||||
const seedPhrase = await driver.findElement(By.css('.twelve-word-phrase')).getText()
|
||||
assert.equal(seedPhrase.split(' ').length, 12)
|
||||
const continueAfterSeedPhrase = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > button:nth-child(4)'))
|
||||
assert.equal(await continueAfterSeedPhrase.getText(), `I'VE COPIED IT SOMEWHERE SAFE`)
|
||||
await continueAfterSeedPhrase.click()
|
||||
await delay(300)
|
||||
})
|
||||
|
||||
it('shows account address', async function () {
|
||||
accountAddress = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div > div:nth-child(1) > flex-column > div.flex-row > div')).getText()
|
||||
})
|
||||
|
||||
it('logs out of the vault', async () => {
|
||||
await driver.findElement(By.css('.sandwich-expando')).click()
|
||||
await delay(500)
|
||||
const logoutButton = await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)'))
|
||||
assert.equal(await logoutButton.getText(), 'Log Out')
|
||||
await logoutButton.click()
|
||||
})
|
||||
|
||||
it('accepts account password after lock', async () => {
|
||||
await delay(500)
|
||||
await driver.findElement(By.id('password-box')).sendKeys('123456789')
|
||||
await driver.findElement(By.css('button')).click()
|
||||
await delay(500)
|
||||
})
|
||||
|
||||
it('shows QR code option', async () => {
|
||||
await delay(300)
|
||||
await driver.findElement(By.css('.fa-ellipsis-h')).click()
|
||||
await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div:nth-child(1) > flex-column > div.name-label > div > span > i > div > div > li:nth-child(3)')).click()
|
||||
await delay(300)
|
||||
})
|
||||
|
||||
it('checks QR code address is the same as account details address', async () => {
|
||||
const QRaccountAddress = await driver.findElement(By.css('.ellip-address')).getText()
|
||||
assert.equal(accountAddress.toLowerCase(), QRaccountAddress)
|
||||
await driver.findElement(By.css('.fa-arrow-left')).click()
|
||||
await delay(500)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Import Ganache seed phrase', function () {
|
||||
it('logs out', async function () {
|
||||
await driver.findElement(By.css('.sandwich-expando')).click()
|
||||
await delay(200)
|
||||
const logOut = await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)'))
|
||||
assert.equal(await logOut.getText(), 'Log Out')
|
||||
await logOut.click()
|
||||
await delay(300)
|
||||
})
|
||||
|
||||
it('restores from seed phrase', async function () {
|
||||
const restoreSeedLink = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div.flex-row.flex-center.flex-grow > p'))
|
||||
assert.equal(await restoreSeedLink.getText(), 'Restore from seed phrase')
|
||||
await restoreSeedLink.click()
|
||||
await delay(100)
|
||||
})
|
||||
|
||||
it('adds seed phrase', async function () {
|
||||
const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent'
|
||||
const seedTextArea = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > textarea'))
|
||||
await seedTextArea.sendKeys(testSeedPhrase)
|
||||
|
||||
await driver.findElement(By.id('password-box')).sendKeys('123456789')
|
||||
await driver.findElement(By.id('password-box-confirm')).sendKeys('123456789')
|
||||
await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div > button:nth-child(2)')).click()
|
||||
await delay(500)
|
||||
})
|
||||
|
||||
it('balance renders', async function () {
|
||||
await delay(200)
|
||||
const balance = await driver.findElement(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)'))
|
||||
assert.equal(await balance.getText(), '100.000')
|
||||
await delay(200)
|
||||
})
|
||||
|
||||
it('sends transaction', async function () {
|
||||
const sendButton = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > button:nth-child(4)'))
|
||||
assert.equal(await sendButton.getText(), 'SEND')
|
||||
await sendButton.click()
|
||||
await delay(200)
|
||||
})
|
||||
|
||||
it('adds recipient address and amount', async function () {
|
||||
const sendTranscationScreen = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > h3:nth-child(2)')).getText()
|
||||
assert.equal(sendTranscationScreen, 'SEND TRANSACTION')
|
||||
const inputAddress = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(3) > div > input'))
|
||||
const inputAmmount = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(4) > input'))
|
||||
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
|
||||
await inputAmmount.sendKeys('10')
|
||||
await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(4) > button')).click()
|
||||
await delay(300)
|
||||
})
|
||||
|
||||
it('confirms transaction', async function () {
|
||||
await delay(300)
|
||||
await driver.findElement(By.css('#pending-tx-form > div.flex-row.flex-space-around.conf-buttons > input')).click()
|
||||
await delay(500)
|
||||
})
|
||||
|
||||
it('finds the transaction in the transactions list', async function () {
|
||||
const tranasactionAmount = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > section > section > div > div > div > div.ether-balance.ether-balance-amount > div > div > div > div:nth-child(1)'))
|
||||
assert.equal(await tranasactionAmount.getText(), '10.0')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Token Factory', function () {
|
||||
|
||||
it('navigates to token factory', async function () {
|
||||
await driver.get('http://tokenfactory.surge.sh/')
|
||||
})
|
||||
|
||||
it('navigates to create token contract link', async function () {
|
||||
const createToken = await driver.findElement(By.css('#bs-example-navbar-collapse-1 > ul > li:nth-child(3) > a'))
|
||||
await createToken.click()
|
||||
})
|
||||
|
||||
it('adds input for token', async function () {
|
||||
const totalSupply = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(5) > input'))
|
||||
const tokenName = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(6) > input'))
|
||||
const tokenDecimal = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(7) > input'))
|
||||
const tokenSymbol = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(8) > input'))
|
||||
const createToken = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > button'))
|
||||
|
||||
await totalSupply.sendKeys('100')
|
||||
await tokenName.sendKeys('Test')
|
||||
await tokenDecimal.sendKeys('0')
|
||||
await tokenSymbol.sendKeys('TST')
|
||||
await createToken.click()
|
||||
await delay(1000)
|
||||
})
|
||||
|
||||
it('confirms transaction in MetaMask popup', async function () {
|
||||
const windowHandles = await driver.getAllWindowHandles()
|
||||
await driver.switchTo().window(windowHandles[windowHandles.length - 1])
|
||||
const metamaskSubmit = await driver.findElement(By.css('#pending-tx-form > div.flex-row.flex-space-around.conf-buttons > input'))
|
||||
await metamaskSubmit.click()
|
||||
await delay(1000)
|
||||
})
|
||||
|
||||
it('switches back to Token Factory to grab the token contract address', async function () {
|
||||
const windowHandles = await driver.getAllWindowHandles()
|
||||
await driver.switchTo().window(windowHandles[0])
|
||||
const tokenContactAddress = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > span:nth-child(3)'))
|
||||
tokenAddress = await tokenContactAddress.getText()
|
||||
await delay(500)
|
||||
})
|
||||
|
||||
it('navigates back to MetaMask popup in the tab', async function () {
|
||||
await driver.get(`chrome-extension://${extensionId}/popup.html`)
|
||||
await delay(700)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Add Token', function () {
|
||||
it('switches to the add token screen', async function () {
|
||||
const tokensTab = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section > div > div.inactiveForm.pointer'))
|
||||
assert.equal(await tokensTab.getText(), 'TOKENS')
|
||||
await tokensTab.click()
|
||||
await delay(300)
|
||||
})
|
||||
|
||||
it('navigates to the add token screen', async function () {
|
||||
const addTokenButton = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section > div.full-flex-height > div > button'))
|
||||
assert.equal(await addTokenButton.getText(), 'ADD TOKEN')
|
||||
await addTokenButton.click()
|
||||
})
|
||||
|
||||
it('checks add token screen rendered', async function () {
|
||||
const addTokenScreen = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.section-title.flex-row.flex-center > h2'))
|
||||
assert.equal(await addTokenScreen.getText(), 'ADD TOKEN')
|
||||
})
|
||||
|
||||
it('adds token parameters', async function () {
|
||||
const tokenContractAddress = await driver.findElement(By.css('#token-address'))
|
||||
await tokenContractAddress.sendKeys(tokenAddress)
|
||||
await delay(300)
|
||||
await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-justify-center.flex-grow.select-none > div > button')).click()
|
||||
await delay(100)
|
||||
})
|
||||
|
||||
it('checks the token balance', async function () {
|
||||
const tokenBalance = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > section > div.full-flex-height > ol > li:nth-child(2) > h3'))
|
||||
assert.equal(await tokenBalance.getText(), '100 TST')
|
||||
})
|
||||
})
|
||||
|
||||
async function getExtensionId () {
|
||||
const extension = await driver.executeScript('return document.querySelector("extensions-manager").shadowRoot.querySelector("extensions-view-manager extensions-item-list").shadowRoot.querySelector("extensions-item:nth-child(2)").getAttribute("id")')
|
||||
return extension
|
||||
}
|
||||
|
||||
async function setProviderType (type) {
|
||||
await driver.executeScript('window.metamask.setProviderType(arguments[0])', type)
|
||||
}
|
||||
|
||||
async function verboseReportOnFailure (test) {
|
||||
const artifactDir = `./test-artifacts/chrome/${test.title}`
|
||||
const filepathBase = `${artifactDir}/test-failure`
|
||||
await pify(mkdirp)(artifactDir)
|
||||
// capture screenshot
|
||||
const screenshot = await driver.takeScreenshot()
|
||||
await pify(fs.writeFile)(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' })
|
||||
// capture dom source
|
||||
const htmlSource = await driver.getPageSource()
|
||||
await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource)
|
||||
}
|
||||
|
||||
})
|
@ -1,13 +1,24 @@
|
||||
require('chromedriver')
|
||||
require('geckodriver')
|
||||
const path = require('path')
|
||||
const webdriver = require('selenium-webdriver')
|
||||
const Command = require('selenium-webdriver/lib/command').Command
|
||||
const By = webdriver.By
|
||||
|
||||
exports.delay = function delay (time) {
|
||||
module.exports = {
|
||||
delay,
|
||||
buildChromeWebDriver,
|
||||
buildFirefoxWebdriver,
|
||||
installWebExt,
|
||||
getExtensionIdChrome,
|
||||
getExtensionIdFirefox,
|
||||
}
|
||||
|
||||
function delay (time) {
|
||||
return new Promise(resolve => setTimeout(resolve, time))
|
||||
}
|
||||
|
||||
|
||||
exports.buildChromeWebDriver = function buildChromeWebDriver (extPath) {
|
||||
function buildChromeWebDriver (extPath) {
|
||||
return new webdriver.Builder()
|
||||
.withCapabilities({
|
||||
chromeOptions: {
|
||||
@ -17,6 +28,29 @@ exports.buildChromeWebDriver = function buildChromeWebDriver (extPath) {
|
||||
.build()
|
||||
}
|
||||
|
||||
exports.buildFirefoxWebdriver = function buildFirefoxWebdriver (extPath) {
|
||||
function buildFirefoxWebdriver () {
|
||||
return new webdriver.Builder().build()
|
||||
}
|
||||
|
||||
async function getExtensionIdChrome (driver) {
|
||||
await driver.get('chrome://extensions')
|
||||
const extensionId = await driver.executeScript('return document.querySelector("extensions-manager").shadowRoot.querySelector("extensions-view-manager extensions-item-list").shadowRoot.querySelector("extensions-item:nth-child(2)").getAttribute("id")')
|
||||
return extensionId
|
||||
}
|
||||
|
||||
async function getExtensionIdFirefox (driver) {
|
||||
await driver.get('about:debugging#addons')
|
||||
const extensionId = await driver.findElement(By.css('dd.addon-target-info-content:nth-child(6) > span:nth-child(1)')).getText()
|
||||
return extensionId
|
||||
}
|
||||
|
||||
async function installWebExt (driver, extension) {
|
||||
const cmd = await new Command('moz-install-web-ext')
|
||||
.setParameter('path', path.resolve(extension))
|
||||
.setParameter('temporary', true)
|
||||
|
||||
await driver.getExecutor()
|
||||
.defineCommand(cmd.getName(), 'POST', '/session/:sessionId/moz/addon/install')
|
||||
|
||||
return await driver.schedule(cmd, 'installWebExt(' + extension + ')')
|
||||
}
|
@ -4,20 +4,29 @@ const path = require('path')
|
||||
const assert = require('assert')
|
||||
const pify = require('pify')
|
||||
const webdriver = require('selenium-webdriver')
|
||||
const Command = require('selenium-webdriver/lib/command').Command
|
||||
const By = webdriver.By
|
||||
const { delay, buildFirefoxWebdriver } = require('../func')
|
||||
const { By, Key } = webdriver
|
||||
const { delay, buildChromeWebDriver, buildFirefoxWebdriver, installWebExt, getExtensionIdChrome, getExtensionIdFirefox } = require('./func')
|
||||
|
||||
describe('', function () {
|
||||
describe('Metamask popup page', function () {
|
||||
let driver, accountAddress, tokenAddress, extensionId
|
||||
|
||||
this.timeout(0)
|
||||
|
||||
before(async function () {
|
||||
const extPath = path.resolve('dist/firefox')
|
||||
driver = buildFirefoxWebdriver()
|
||||
installWebExt(driver, extPath)
|
||||
await delay(700)
|
||||
if (process.env.SELENIUM_BROWSER === 'chrome') {
|
||||
const extPath = path.resolve('dist/chrome')
|
||||
driver = buildChromeWebDriver(extPath)
|
||||
extensionId = await getExtensionIdChrome(driver)
|
||||
await driver.get(`chrome-extension://${extensionId}/popup.html`)
|
||||
|
||||
} else if (process.env.SELENIUM_BROWSER === 'firefox') {
|
||||
const extPath = path.resolve('dist/firefox')
|
||||
driver = buildFirefoxWebdriver()
|
||||
await installWebExt(driver, extPath)
|
||||
await delay(700)
|
||||
extensionId = await getExtensionIdFirefox(driver)
|
||||
await driver.get(`moz-extension://${extensionId}/popup.html`)
|
||||
}
|
||||
})
|
||||
|
||||
afterEach(async function () {
|
||||
@ -32,23 +41,17 @@ describe('', function () {
|
||||
|
||||
describe('Setup', function () {
|
||||
|
||||
it('switches to Firefox addon list', async function () {
|
||||
await driver.get('about:debugging#addons')
|
||||
await delay(1000)
|
||||
})
|
||||
|
||||
it(`selects MetaMask's extension id and opens it in the current tab`, async function () {
|
||||
const tabs = await driver.getAllWindowHandles()
|
||||
await driver.switchTo().window(tabs[0])
|
||||
extensionId = await driver.findElement(By.css('dd.addon-target-info-content:nth-child(6) > span:nth-child(1)')).getText()
|
||||
await driver.get(`moz-extension://${extensionId}/popup.html`)
|
||||
await delay(500)
|
||||
it('switches to Chrome extensions list', async function () {
|
||||
await delay(300)
|
||||
const windowHandles = await driver.getAllWindowHandles()
|
||||
await driver.switchTo().window(windowHandles[0])
|
||||
})
|
||||
|
||||
it('sets provider type to localhost', async function () {
|
||||
await setProviderType('localhost')
|
||||
await delay(300)
|
||||
await setProviderType('localhost')
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('Account Creation', () => {
|
||||
@ -67,10 +70,9 @@ describe('', function () {
|
||||
})
|
||||
|
||||
it('show terms of use', async () => {
|
||||
await delay(300)
|
||||
const terms = await driver.findElement(By.css('.terms-header')).getText()
|
||||
assert.equal(terms, 'TERMS OF USE', 'shows terms of use')
|
||||
await delay(300)
|
||||
delay(300)
|
||||
})
|
||||
|
||||
it('checks if the TOU button is disabled', async () => {
|
||||
@ -78,15 +80,11 @@ describe('', function () {
|
||||
assert.equal(button, false, 'disabled continue button')
|
||||
const element = await driver.findElement(By.linkText('Attributions'))
|
||||
await driver.executeScript('arguments[0].scrollIntoView(true)', element)
|
||||
await delay(300)
|
||||
await delay(700)
|
||||
})
|
||||
|
||||
it('allows the button to be clicked when scrolled to the bottom of TOU', async () => {
|
||||
const button = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > button'))
|
||||
await delay(300)
|
||||
const buttonEnabled = await button.isEnabled()
|
||||
assert.equal(buttonEnabled, true, 'enabled continue button')
|
||||
await delay(200)
|
||||
await button.click()
|
||||
})
|
||||
|
||||
@ -126,7 +124,7 @@ describe('', function () {
|
||||
it('accepts account password after lock', async () => {
|
||||
await delay(500)
|
||||
await driver.findElement(By.id('password-box')).sendKeys('123456789')
|
||||
await driver.findElement(By.id('password-box')).sendKeys(webdriver.Key.ENTER)
|
||||
await driver.findElement(By.id('password-box')).sendKeys(Key.ENTER)
|
||||
await delay(500)
|
||||
})
|
||||
|
||||
@ -146,6 +144,7 @@ describe('', function () {
|
||||
})
|
||||
|
||||
describe('Import Ganache seed phrase', function () {
|
||||
|
||||
it('logs out', async function () {
|
||||
await driver.findElement(By.css('.sandwich-expando')).click()
|
||||
await delay(200)
|
||||
@ -236,7 +235,7 @@ describe('', function () {
|
||||
await delay(1000)
|
||||
})
|
||||
|
||||
// There is an issue with blank confirmation window, but the button is still there and the driver is able to clicked (?.?)
|
||||
// There is an issue with blank confirmation window in Firefox, but the button is still there and the driver is able to clicked (?.?)
|
||||
it('confirms transaction in MetaMask popup', async function () {
|
||||
const windowHandles = await driver.getAllWindowHandles()
|
||||
await driver.switchTo().window(windowHandles[windowHandles.length - 1])
|
||||
@ -254,12 +253,17 @@ describe('', function () {
|
||||
})
|
||||
|
||||
it('navigates back to MetaMask popup in the tab', async function () {
|
||||
await driver.get(`moz-extension://${extensionId}/popup.html`)
|
||||
if (process.env.SELENIUM_BROWSER === 'chrome') {
|
||||
await driver.get(`chrome-extension://${extensionId}/popup.html`)
|
||||
} else if (process.env.SELENIUM_BROWSER === 'firefox') {
|
||||
await driver.get(`moz-extension://${extensionId}/popup.html`)
|
||||
}
|
||||
await delay(700)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Add Token', function () {
|
||||
|
||||
it('switches to the add token screen', async function () {
|
||||
const tokensTab = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section > div > div.inactiveForm.pointer'))
|
||||
assert.equal(await tokensTab.getText(), 'TOKENS')
|
||||
@ -283,7 +287,7 @@ describe('', function () {
|
||||
await tokenContractAddress.sendKeys(tokenAddress)
|
||||
await delay(300)
|
||||
await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-justify-center.flex-grow.select-none > div > button')).click()
|
||||
await delay(100)
|
||||
await delay(200)
|
||||
})
|
||||
|
||||
it('checks the token balance', async function () {
|
||||
@ -292,12 +296,17 @@ describe('', function () {
|
||||
})
|
||||
})
|
||||
|
||||
async function setProviderType(type) {
|
||||
async function setProviderType (type) {
|
||||
await driver.executeScript('window.metamask.setProviderType(arguments[0])', type)
|
||||
}
|
||||
|
||||
async function verboseReportOnFailure(test) {
|
||||
const artifactDir = `./test-artifacts/firefox/${test.title}`
|
||||
async function verboseReportOnFailure (test) {
|
||||
let artifactDir
|
||||
if (process.env.SELENIUM_BROWSER === 'chrome') {
|
||||
artifactDir = `./test-artifacts/chrome/${test.title}`
|
||||
} else if (process.env.SELENIUM_BROWSER === 'firefox') {
|
||||
artifactDir = `./test-artifacts/firefox/${test.title}`
|
||||
}
|
||||
const filepathBase = `${artifactDir}/test-failure`
|
||||
await pify(mkdirp)(artifactDir)
|
||||
// capture screenshot
|
||||
@ -309,15 +318,3 @@ describe('', function () {
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
async function installWebExt (driver, extension) {
|
||||
const cmd = await new Command('moz-install-web-ext')
|
||||
.setParameter('path', path.resolve(extension))
|
||||
.setParameter('temporary', true)
|
||||
|
||||
await driver.getExecutor()
|
||||
.defineCommand(cmd.getName(), 'POST', '/session/:sessionId/moz/addon/install')
|
||||
|
||||
return await driver.schedule(cmd, 'installWebExt(' + extension + ')')
|
||||
}
|
||||
|
@ -1,35 +0,0 @@
|
||||
// var jsdom = require('mocha-jsdom')
|
||||
var assert = require('assert')
|
||||
var freeze = require('deep-freeze-strict')
|
||||
var path = require('path')
|
||||
|
||||
var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js'))
|
||||
var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
|
||||
|
||||
describe('SAVE_ACCOUNT_LABEL', function () {
|
||||
it('updates the state.metamask.identities[:i].name property of the state to the action.value.label', function () {
|
||||
var initialState = {
|
||||
metamask: {
|
||||
identities: {
|
||||
foo: {
|
||||
name: 'bar',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
freeze(initialState)
|
||||
|
||||
const action = {
|
||||
type: actions.SAVE_ACCOUNT_LABEL,
|
||||
value: {
|
||||
account: 'foo',
|
||||
label: 'baz',
|
||||
},
|
||||
}
|
||||
freeze(action)
|
||||
|
||||
var resultingState = reducers(initialState, action)
|
||||
assert.equal(resultingState.metamask.identities.foo.name, action.value.label)
|
||||
})
|
||||
})
|
||||
|
34
test/unit/actions/set_account_label_test.js
Normal file
34
test/unit/actions/set_account_label_test.js
Normal file
@ -0,0 +1,34 @@
|
||||
const assert = require('assert')
|
||||
const freeze = require('deep-freeze-strict')
|
||||
const path = require('path')
|
||||
|
||||
const actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js'))
|
||||
const reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js'))
|
||||
|
||||
describe('SET_ACCOUNT_LABEL', function () {
|
||||
it('updates the state.metamask.identities[:i].name property of the state to the action.value.label', function () {
|
||||
const initialState = {
|
||||
metamask: {
|
||||
identities: {
|
||||
foo: {
|
||||
name: 'bar',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
freeze(initialState)
|
||||
|
||||
const action = {
|
||||
type: actions.SET_ACCOUNT_LABEL,
|
||||
value: {
|
||||
account: 'foo',
|
||||
label: 'baz',
|
||||
},
|
||||
}
|
||||
freeze(action)
|
||||
|
||||
const resultingState = reducers(initialState, action)
|
||||
assert.equal(resultingState.metamask.identities.foo.name, action.value.label)
|
||||
})
|
||||
})
|
||||
|
@ -1,26 +1,26 @@
|
||||
const assert = require('assert')
|
||||
const AddressBookController = require('../../app/scripts/controllers/address-book')
|
||||
|
||||
const mockKeyringController = {
|
||||
memStore: {
|
||||
getState: function () {
|
||||
return {
|
||||
identities: {
|
||||
'0x0aaa': {
|
||||
address: '0x0aaa',
|
||||
name: 'owned',
|
||||
},
|
||||
const stubPreferencesStore = {
|
||||
getState: function () {
|
||||
return {
|
||||
identities: {
|
||||
'0x0aaa': {
|
||||
address: '0x0aaa',
|
||||
name: 'owned',
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
describe('address-book-controller', function () {
|
||||
var addressBookController
|
||||
|
||||
beforeEach(function () {
|
||||
addressBookController = new AddressBookController({}, mockKeyringController)
|
||||
addressBookController = new AddressBookController({
|
||||
preferencesStore: stubPreferencesStore,
|
||||
})
|
||||
})
|
||||
|
||||
describe('addres book management', function () {
|
||||
|
@ -106,7 +106,7 @@ describe('MetaMaskController', function () {
|
||||
[TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL },
|
||||
})
|
||||
|
||||
await metamaskController.keyringController.saveAccountLabel(TEST_ADDRESS, 'Account Foo')
|
||||
await metamaskController.preferencesController.setAccountLabel(TEST_ADDRESS, 'Account Foo')
|
||||
assert.deepEqual(metamaskController.getState().identities, {
|
||||
[TEST_ADDRESS]: { address: TEST_ADDRESS, name: 'Account Foo' },
|
||||
})
|
||||
|
41
test/unit/migrations/026-test.js
Normal file
41
test/unit/migrations/026-test.js
Normal file
@ -0,0 +1,41 @@
|
||||
const assert = require('assert')
|
||||
const migration26 = require('../../../app/scripts/migrations/026')
|
||||
const oldStorage = {
|
||||
'meta': {'version': 25},
|
||||
'data': {
|
||||
'PreferencesController': {},
|
||||
'KeyringController': {
|
||||
'walletNicknames': {
|
||||
'0x1e77e2': 'Test Account 1',
|
||||
'0x7e57e2': 'Test Account 2',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
describe('migration #26', () => {
|
||||
it('should move the identities from KeyringController', (done) => {
|
||||
migration26.migrate(oldStorage)
|
||||
.then((newStorage) => {
|
||||
const identities = newStorage.data.PreferencesController.identities
|
||||
assert.deepEqual(identities, {
|
||||
'0x1e77e2': {name: 'Test Account 1', address: '0x1e77e2'},
|
||||
'0x7e57e2': {name: 'Test Account 2', address: '0x7e57e2'},
|
||||
})
|
||||
assert.strictEqual(newStorage.data.KeyringController.walletNicknames, undefined)
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
||||
it('should successfully migrate first time state', (done) => {
|
||||
migration26.migrate({
|
||||
meta: {},
|
||||
data: require('../../../app/scripts/first-time-state'),
|
||||
})
|
||||
.then((migratedData) => {
|
||||
assert.equal(migratedData.meta.version, migration26.version)
|
||||
done()
|
||||
}).catch(done)
|
||||
})
|
||||
})
|
@ -4,16 +4,91 @@ const PreferencesController = require('../../app/scripts/controllers/preferences
|
||||
describe('preferences controller', function () {
|
||||
let preferencesController
|
||||
|
||||
before(() => {
|
||||
beforeEach(() => {
|
||||
preferencesController = new PreferencesController()
|
||||
})
|
||||
|
||||
describe('setAddresses', function () {
|
||||
it('should keep a map of addresses to names and addresses in the store', function () {
|
||||
preferencesController.setAddresses([
|
||||
'0xda22le',
|
||||
'0x7e57e2',
|
||||
])
|
||||
|
||||
const {identities} = preferencesController.store.getState()
|
||||
assert.deepEqual(identities, {
|
||||
'0xda22le': {
|
||||
name: 'Account 1',
|
||||
address: '0xda22le',
|
||||
},
|
||||
'0x7e57e2': {
|
||||
name: 'Account 2',
|
||||
address: '0x7e57e2',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should replace its list of addresses', function () {
|
||||
preferencesController.setAddresses([
|
||||
'0xda22le',
|
||||
'0x7e57e2',
|
||||
])
|
||||
preferencesController.setAddresses([
|
||||
'0xda22le77',
|
||||
'0x7e57e277',
|
||||
])
|
||||
|
||||
const {identities} = preferencesController.store.getState()
|
||||
assert.deepEqual(identities, {
|
||||
'0xda22le77': {
|
||||
name: 'Account 1',
|
||||
address: '0xda22le77',
|
||||
},
|
||||
'0x7e57e277': {
|
||||
name: 'Account 2',
|
||||
address: '0x7e57e277',
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('setAccountLabel', function () {
|
||||
it('should update a label for the given account', function () {
|
||||
preferencesController.setAddresses([
|
||||
'0xda22le',
|
||||
'0x7e57e2',
|
||||
])
|
||||
|
||||
assert.deepEqual(preferencesController.store.getState().identities['0xda22le'], {
|
||||
name: 'Account 1',
|
||||
address: '0xda22le',
|
||||
})
|
||||
|
||||
|
||||
preferencesController.setAccountLabel('0xda22le', 'Dazzle')
|
||||
assert.deepEqual(preferencesController.store.getState().identities['0xda22le'], {
|
||||
name: 'Dazzle',
|
||||
address: '0xda22le',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getTokens', function () {
|
||||
it('should return an empty list initially', async function () {
|
||||
await preferencesController.setSelectedAddress('0x7e57e2')
|
||||
|
||||
const tokens = preferencesController.getTokens()
|
||||
assert.equal(tokens.length, 0, 'empty list of tokens')
|
||||
})
|
||||
})
|
||||
|
||||
describe('addToken', function () {
|
||||
it('should add that token to its state', async function () {
|
||||
const address = '0xabcdef1234567'
|
||||
const symbol = 'ABBR'
|
||||
const decimals = 5
|
||||
|
||||
await preferencesController.setSelectedAddress('0x7e57e2')
|
||||
await preferencesController.addToken(address, symbol, decimals)
|
||||
|
||||
const tokens = preferencesController.getTokens()
|
||||
@ -30,6 +105,7 @@ describe('preferences controller', function () {
|
||||
const symbol = 'ABBR'
|
||||
const decimals = 5
|
||||
|
||||
await preferencesController.setSelectedAddress('0x7e57e2')
|
||||
await preferencesController.addToken(address, symbol, decimals)
|
||||
|
||||
const newDecimals = 6
|
||||
@ -43,6 +119,44 @@ describe('preferences controller', function () {
|
||||
assert.equal(added.symbol, symbol, 'set symbol correctly')
|
||||
assert.equal(added.decimals, newDecimals, 'updated decimals correctly')
|
||||
})
|
||||
|
||||
it('should allow adding tokens to two separate addresses', async function () {
|
||||
const address = '0xabcdef1234567'
|
||||
const symbol = 'ABBR'
|
||||
const decimals = 5
|
||||
|
||||
await preferencesController.setSelectedAddress('0x7e57e2')
|
||||
await preferencesController.addToken(address, symbol, decimals)
|
||||
assert.equal(preferencesController.getTokens().length, 1, 'one token added for 1st address')
|
||||
|
||||
await preferencesController.setSelectedAddress('0xda22le')
|
||||
await preferencesController.addToken(address, symbol, decimals)
|
||||
assert.equal(preferencesController.getTokens().length, 1, 'one token added for 2nd address')
|
||||
})
|
||||
})
|
||||
|
||||
describe('removeToken', function () {
|
||||
it('should remove the only token from its state', async function () {
|
||||
await preferencesController.setSelectedAddress('0x7e57e2')
|
||||
await preferencesController.addToken('0xa', 'A', 5)
|
||||
await preferencesController.removeToken('0xa')
|
||||
|
||||
const tokens = preferencesController.getTokens()
|
||||
assert.equal(tokens.length, 0, 'one token removed')
|
||||
})
|
||||
|
||||
it('should remove a token from its state', async function () {
|
||||
await preferencesController.setSelectedAddress('0x7e57e2')
|
||||
await preferencesController.addToken('0xa', 'A', 4)
|
||||
await preferencesController.addToken('0xb', 'B', 5)
|
||||
await preferencesController.removeToken('0xa')
|
||||
|
||||
const tokens = preferencesController.getTokens()
|
||||
assert.equal(tokens.length, 1, 'one token removed')
|
||||
|
||||
const [token1] = tokens
|
||||
assert.deepEqual(token1, {address: '0xb', symbol: 'B', decimals: 5})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -24,7 +24,7 @@ function mapDispatchToProps (dispatch) {
|
||||
dispatch(actions.showModal({ name: 'EXPORT_PRIVATE_KEY' }))
|
||||
},
|
||||
hideModal: () => dispatch(actions.hideModal()),
|
||||
saveAccountLabel: (address, label) => dispatch(actions.saveAccountLabel(address, label)),
|
||||
setAccountLabel: (address, label) => dispatch(actions.setAccountLabel(address, label)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,8 +124,8 @@ var actions = {
|
||||
SHOW_PRIVATE_KEY: 'SHOW_PRIVATE_KEY',
|
||||
showPrivateKey: showPrivateKey,
|
||||
exportAccountComplete,
|
||||
SAVE_ACCOUNT_LABEL: 'SAVE_ACCOUNT_LABEL',
|
||||
saveAccountLabel: saveAccountLabel,
|
||||
SET_ACCOUNT_LABEL: 'SET_ACCOUNT_LABEL',
|
||||
setAccountLabel,
|
||||
// tx conf screen
|
||||
COMPLETED_TX: 'COMPLETED_TX',
|
||||
TRANSACTION_ERROR: 'TRANSACTION_ERROR',
|
||||
@ -1598,13 +1598,13 @@ function showPrivateKey (key) {
|
||||
}
|
||||
}
|
||||
|
||||
function saveAccountLabel (account, label) {
|
||||
function setAccountLabel (account, label) {
|
||||
return (dispatch) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
log.debug(`background.saveAccountLabel`)
|
||||
log.debug(`background.setAccountLabel`)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
background.saveAccountLabel(account, label, (err) => {
|
||||
background.setAccountLabel(account, label, (err) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
|
||||
if (err) {
|
||||
@ -1613,7 +1613,7 @@ function saveAccountLabel (account, label) {
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: actions.SAVE_ACCOUNT_LABEL,
|
||||
type: actions.SET_ACCOUNT_LABEL,
|
||||
value: { account, label },
|
||||
})
|
||||
|
||||
|
@ -25,7 +25,7 @@ function mapDispatchToProps (dispatch) {
|
||||
dispatch(actions.showModal({ name: 'EXPORT_PRIVATE_KEY' }))
|
||||
},
|
||||
hideModal: () => dispatch(actions.hideModal()),
|
||||
saveAccountLabel: (address, label) => dispatch(actions.saveAccountLabel(address, label)),
|
||||
setAccountLabel: (address, label) => dispatch(actions.setAccountLabel(address, label)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +49,7 @@ AccountDetailsModal.prototype.render = function () {
|
||||
selectedIdentity,
|
||||
network,
|
||||
showExportPrivateKeyModal,
|
||||
saveAccountLabel,
|
||||
setAccountLabel,
|
||||
} = this.props
|
||||
const { name, address } = selectedIdentity
|
||||
|
||||
@ -57,7 +57,7 @@ AccountDetailsModal.prototype.render = function () {
|
||||
h(EditableLabel, {
|
||||
className: 'account-modal__name',
|
||||
defaultValue: name,
|
||||
onSubmit: label => saveAccountLabel(address, label),
|
||||
onSubmit: label => setAccountLabel(address, label),
|
||||
}),
|
||||
|
||||
h(QrView, {
|
||||
|
@ -18,8 +18,8 @@ function mapDispatchToProps (dispatch) {
|
||||
hideModal: () => {
|
||||
dispatch(actions.hideModal())
|
||||
},
|
||||
saveAccountLabel: (account, label) => {
|
||||
dispatch(actions.saveAccountLabel(account, label))
|
||||
setAccountLabel: (account, label) => {
|
||||
dispatch(actions.setAccountLabel(account, label))
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -41,7 +41,7 @@ module.exports = connect(mapStateToProps, mapDispatchToProps)(EditAccountNameMod
|
||||
|
||||
|
||||
EditAccountNameModal.prototype.render = function () {
|
||||
const { hideModal, saveAccountLabel, identity } = this.props
|
||||
const { hideModal, setAccountLabel, identity } = this.props
|
||||
|
||||
return h('div', {}, [
|
||||
h('div.flex-column.edit-account-name-modal-content', {
|
||||
@ -69,7 +69,7 @@ EditAccountNameModal.prototype.render = function () {
|
||||
h('button.btn-clear.edit-account-name-modal-save-button.allcaps', {
|
||||
onClick: () => {
|
||||
if (this.state.inputText.length !== 0) {
|
||||
saveAccountLabel(identity.address, this.state.inputText)
|
||||
setAccountLabel(identity.address, this.state.inputText)
|
||||
hideModal()
|
||||
}
|
||||
},
|
||||
|
@ -95,7 +95,7 @@ const mapDispatchToProps = dispatch => {
|
||||
dispatch(actions.addNewAccount())
|
||||
.then((newAccountAddress) => {
|
||||
if (newAccountName) {
|
||||
dispatch(actions.saveAccountLabel(newAccountAddress, newAccountName))
|
||||
dispatch(actions.setAccountLabel(newAccountAddress, newAccountName))
|
||||
}
|
||||
dispatch(actions.hideModal())
|
||||
})
|
||||
|
@ -75,7 +75,7 @@ const mapDispatchToProps = dispatch => ({
|
||||
dispatch(actions.showModal({ name: 'EXPORT_PRIVATE_KEY' }))
|
||||
},
|
||||
hideModal: () => dispatch(actions.hideModal()),
|
||||
saveAccountLabel: (address, label) => dispatch(actions.saveAccountLabel(address, label)),
|
||||
setAccountLabel: (address, label) => dispatch(actions.setAccountLabel(address, label)),
|
||||
})
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(CreateAccountPage)
|
||||
|
@ -87,7 +87,7 @@ const mapDispatchToProps = dispatch => {
|
||||
return dispatch(actions.addNewAccount())
|
||||
.then(newAccountAddress => {
|
||||
if (newAccountName) {
|
||||
dispatch(actions.saveAccountLabel(newAccountAddress, newAccountName))
|
||||
dispatch(actions.setAccountLabel(newAccountAddress, newAccountName))
|
||||
}
|
||||
})
|
||||
},
|
||||
|
@ -566,7 +566,6 @@
|
||||
padding: 30px;
|
||||
font-size: 22px;
|
||||
color: $nile-blue;
|
||||
height: 79px;
|
||||
}
|
||||
|
||||
&__message {
|
||||
@ -832,7 +831,6 @@
|
||||
padding: 30px;
|
||||
font-size: 22px;
|
||||
color: $nile-blue;
|
||||
height: 79px;
|
||||
}
|
||||
|
||||
.notification-modal-message {
|
||||
|
@ -43,8 +43,6 @@
|
||||
}
|
||||
|
||||
&__header__text {
|
||||
height: 29px;
|
||||
width: 179px;
|
||||
color: #5B5D67;
|
||||
font-family: Roboto;
|
||||
font-size: 22px;
|
||||
|
@ -163,7 +163,7 @@ function reduceMetamask (state, action) {
|
||||
selectedTokenAddress: action.value,
|
||||
})
|
||||
|
||||
case actions.SAVE_ACCOUNT_LABEL:
|
||||
case actions.SET_ACCOUNT_LABEL:
|
||||
const account = action.value.account
|
||||
const name = action.value.label
|
||||
const id = {}
|
||||
|
Loading…
Reference in New Issue
Block a user