commit
6bb92a8672
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 670 B |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,158 @@ |
||||
const ObservableStore = require('obs-store') |
||||
|
||||
/** |
||||
* A controller that services user-approved requests for a full Ethereum provider API |
||||
*/ |
||||
class ProviderApprovalController { |
||||
/** |
||||
* Determines if caching is enabled |
||||
*/ |
||||
caching = true |
||||
|
||||
/** |
||||
* Creates a ProviderApprovalController |
||||
* |
||||
* @param {Object} [config] - Options to configure controller |
||||
*/ |
||||
constructor ({ closePopup, keyringController, openPopup, platform, preferencesController, publicConfigStore } = {}) { |
||||
this.approvedOrigins = {} |
||||
this.closePopup = closePopup |
||||
this.keyringController = keyringController |
||||
this.openPopup = openPopup |
||||
this.platform = platform |
||||
this.preferencesController = preferencesController |
||||
this.publicConfigStore = publicConfigStore |
||||
this.store = new ObservableStore() |
||||
|
||||
if (platform && platform.addMessageListener) { |
||||
platform.addMessageListener(({ action = '', force, origin, siteTitle, siteImage }) => { |
||||
switch (action) { |
||||
case 'init-provider-request': |
||||
this._handleProviderRequest(origin, siteTitle, siteImage, force) |
||||
break |
||||
case 'init-is-approved': |
||||
this._handleIsApproved(origin) |
||||
break |
||||
case 'init-is-unlocked': |
||||
this._handleIsUnlocked() |
||||
break |
||||
case 'init-privacy-request': |
||||
this._handlePrivacyRequest() |
||||
break |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Called when a tab requests access to a full Ethereum provider API |
||||
* |
||||
* @param {string} origin - Origin of the window requesting full provider access |
||||
* @param {string} siteTitle - The title of the document requesting full provider access |
||||
* @param {string} siteImage - The icon of the window requesting full provider access |
||||
*/ |
||||
_handleProviderRequest (origin, siteTitle, siteImage, force) { |
||||
this.store.updateState({ providerRequests: [{ origin, siteTitle, siteImage }] }) |
||||
const isUnlocked = this.keyringController.memStore.getState().isUnlocked |
||||
if (!force && this.approvedOrigins[origin] && this.caching && isUnlocked) { |
||||
this.approveProviderRequest(origin) |
||||
return |
||||
} |
||||
this.openPopup && this.openPopup() |
||||
} |
||||
|
||||
/** |
||||
* Called by a tab to determine if an origin has been approved in the past |
||||
* |
||||
* @param {string} origin - Origin of the window |
||||
*/ |
||||
_handleIsApproved (origin) { |
||||
this.platform && this.platform.sendMessage({ |
||||
action: 'answer-is-approved', |
||||
isApproved: this.approvedOrigins[origin] && this.caching, |
||||
caching: this.caching, |
||||
}, { active: true }) |
||||
} |
||||
|
||||
/** |
||||
* Called by a tab to determine if MetaMask is currently locked or unlocked |
||||
*/ |
||||
_handleIsUnlocked () { |
||||
const isUnlocked = this.keyringController.memStore.getState().isUnlocked |
||||
this.platform && this.platform.sendMessage({ action: 'answer-is-unlocked', isUnlocked }, { active: true }) |
||||
} |
||||
|
||||
/** |
||||
* Called to check privacy mode; if privacy mode is off, this will automatically enable the provider (legacy behavior) |
||||
*/ |
||||
_handlePrivacyRequest () { |
||||
const privacyMode = this.preferencesController.getFeatureFlags().privacyMode |
||||
if (!privacyMode) { |
||||
this.platform && this.platform.sendMessage({ |
||||
action: 'approve-legacy-provider-request', |
||||
selectedAddress: this.publicConfigStore.getState().selectedAddress, |
||||
}, { active: true }) |
||||
this.publicConfigStore.emit('update', this.publicConfigStore.getState()) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Called when a user approves access to a full Ethereum provider API |
||||
* |
||||
* @param {string} origin - Origin of the target window to approve provider access |
||||
*/ |
||||
approveProviderRequest (origin) { |
||||
this.closePopup && this.closePopup() |
||||
const requests = this.store.getState().providerRequests || [] |
||||
this.platform && this.platform.sendMessage({ |
||||
action: 'approve-provider-request', |
||||
selectedAddress: this.publicConfigStore.getState().selectedAddress, |
||||
}, { active: true }) |
||||
this.publicConfigStore.emit('update', this.publicConfigStore.getState()) |
||||
const providerRequests = requests.filter(request => request.origin !== origin) |
||||
this.store.updateState({ providerRequests }) |
||||
this.approvedOrigins[origin] = true |
||||
} |
||||
|
||||
/** |
||||
* Called when a tab rejects access to a full Ethereum provider API |
||||
* |
||||
* @param {string} origin - Origin of the target window to reject provider access |
||||
*/ |
||||
rejectProviderRequest (origin) { |
||||
this.closePopup && this.closePopup() |
||||
const requests = this.store.getState().providerRequests || [] |
||||
this.platform && this.platform.sendMessage({ action: 'reject-provider-request' }, { active: true }) |
||||
const providerRequests = requests.filter(request => request.origin !== origin) |
||||
this.store.updateState({ providerRequests }) |
||||
delete this.approvedOrigins[origin] |
||||
} |
||||
|
||||
/** |
||||
* Clears any cached approvals for user-approved origins |
||||
*/ |
||||
clearApprovedOrigins () { |
||||
this.approvedOrigins = {} |
||||
} |
||||
|
||||
/** |
||||
* Determines if a given origin should have accounts exposed |
||||
* |
||||
* @param {string} origin - Domain origin to check for approval status |
||||
* @returns {boolean} - True if the origin has been approved |
||||
*/ |
||||
shouldExposeAccounts (origin) { |
||||
const privacyMode = this.preferencesController.getFeatureFlags().privacyMode |
||||
return !privacyMode || this.approvedOrigins[origin] |
||||
} |
||||
|
||||
/** |
||||
* Tells all tabs that MetaMask is now locked. This is primarily used to set |
||||
* internal flags in the contentscript and inpage script. |
||||
*/ |
||||
setLocked () { |
||||
this.platform.sendMessage({ action: 'metamask-set-locked' }) |
||||
} |
||||
} |
||||
|
||||
module.exports = ProviderApprovalController |
@ -0,0 +1,54 @@ |
||||
const namehash = require('eth-ens-namehash') |
||||
const multihash = require('multihashes') |
||||
const Eth = require('ethjs-query') |
||||
const EthContract = require('ethjs-contract') |
||||
const registrarAbi = require('./contracts/registrar') |
||||
const resolverAbi = require('./contracts/resolver') |
||||
|
||||
module.exports = resolveEnsToIpfsContentId |
||||
|
||||
|
||||
async function resolveEnsToIpfsContentId ({ provider, name }) { |
||||
const eth = new Eth(provider) |
||||
const hash = namehash.hash(name) |
||||
const contract = new EthContract(eth) |
||||
// lookup registrar
|
||||
const chainId = Number.parseInt(await eth.net_version(), 10) |
||||
const registrarAddress = getRegistrarForChainId(chainId) |
||||
if (!registrarAddress) { |
||||
throw new Error(`EnsIpfsResolver - no known ens-ipfs registrar for chainId "${chainId}"`) |
||||
} |
||||
const Registrar = contract(registrarAbi).at(registrarAddress) |
||||
// lookup resolver
|
||||
const resolverLookupResult = await Registrar.resolver(hash) |
||||
const resolverAddress = resolverLookupResult[0] |
||||
if (hexValueIsEmpty(resolverAddress)) { |
||||
throw new Error(`EnsIpfsResolver - no resolver found for name "${name}"`) |
||||
} |
||||
const Resolver = contract(resolverAbi).at(resolverAddress) |
||||
// lookup content id
|
||||
const contentLookupResult = await Resolver.content(hash) |
||||
const contentHash = contentLookupResult[0] |
||||
if (hexValueIsEmpty(contentHash)) { |
||||
throw new Error(`EnsIpfsResolver - no content ID found for name "${name}"`) |
||||
} |
||||
const nonPrefixedHex = contentHash.slice(2) |
||||
const buffer = multihash.fromHexString(nonPrefixedHex) |
||||
const contentId = multihash.toB58String(multihash.encode(buffer, 'sha2-256')) |
||||
return contentId |
||||
} |
||||
|
||||
function hexValueIsEmpty(value) { |
||||
return [undefined, null, '0x', '0x0', '0x0000000000000000000000000000000000000000000000000000000000000000'].includes(value) |
||||
} |
||||
|
||||
function getRegistrarForChainId (chainId) { |
||||
switch (chainId) { |
||||
// mainnet
|
||||
case 1: |
||||
return '0x314159265dd8dbb310642f98f50c066173c1259b' |
||||
// ropsten
|
||||
case 3: |
||||
return '0x112234455c3a32fd11230c42e7bccd4a84e02010' |
||||
} |
||||
} |
@ -0,0 +1,63 @@ |
||||
const urlUtil = require('url') |
||||
const extension = require('extensionizer') |
||||
const resolveEnsToIpfsContentId = require('./resolver.js') |
||||
|
||||
const supportedTopLevelDomains = ['eth'] |
||||
|
||||
module.exports = setupEnsIpfsResolver |
||||
|
||||
function setupEnsIpfsResolver({ provider }) { |
||||
|
||||
// install listener
|
||||
const urlPatterns = supportedTopLevelDomains.map(tld => `*://*.${tld}/*`) |
||||
extension.webRequest.onErrorOccurred.addListener(webRequestDidFail, { urls: urlPatterns }) |
||||
|
||||
// return api object
|
||||
return { |
||||
// uninstall listener
|
||||
remove () { |
||||
extension.webRequest.onErrorOccurred.removeListener(webRequestDidFail) |
||||
}, |
||||
} |
||||
|
||||
async function webRequestDidFail (details) { |
||||
const { tabId, url } = details |
||||
// ignore requests that are not associated with tabs
|
||||
if (tabId === -1) return |
||||
// parse ens name
|
||||
const urlData = urlUtil.parse(url) |
||||
const { hostname: name, path, search } = urlData |
||||
const domainParts = name.split('.') |
||||
const topLevelDomain = domainParts[domainParts.length - 1] |
||||
// if unsupported TLD, abort
|
||||
if (!supportedTopLevelDomains.includes(topLevelDomain)) return |
||||
// otherwise attempt resolve
|
||||
attemptResolve({ tabId, name, path, search }) |
||||
} |
||||
|
||||
async function attemptResolve({ tabId, name, path, search }) { |
||||
extension.tabs.update(tabId, { url: `loading.html` }) |
||||
try { |
||||
const ipfsContentId = await resolveEnsToIpfsContentId({ provider, name }) |
||||
let url = `https://gateway.ipfs.io/ipfs/${ipfsContentId}${path}${search || ''}` |
||||
try { |
||||
// check if ipfs gateway has result
|
||||
const response = await fetch(url, { method: 'HEAD' }) |
||||
// if failure, redirect to 404 page
|
||||
if (response.status !== 200) { |
||||
extension.tabs.update(tabId, { url: '404.html' }) |
||||
return |
||||
} |
||||
// otherwise redirect to the correct page
|
||||
extension.tabs.update(tabId, { url }) |
||||
} catch (err) { |
||||
console.warn(err) |
||||
// if HEAD fetch failed, redirect so user can see relevant error page
|
||||
extension.tabs.update(tabId, { url }) |
||||
} |
||||
} catch (err) { |
||||
console.warn(err) |
||||
extension.tabs.update(tabId, { url: `error.html?name=${name}` }) |
||||
} |
||||
} |
||||
} |
@ -1,46 +0,0 @@ |
||||
const extension = require('extensionizer') |
||||
const resolver = require('./resolver.js') |
||||
|
||||
module.exports = function (provider) { |
||||
function ipfsContent (details) { |
||||
const name = details.url.substring(7, details.url.length - 1) |
||||
let clearTime = null |
||||
if (/^.+\.eth$/.test(name) === false) return |
||||
|
||||
extension.tabs.query({active: true}, tab => { |
||||
extension.tabs.update(tab.id, { url: 'loading.html' }) |
||||
|
||||
clearTime = setTimeout(() => { |
||||
return extension.tabs.update(tab.id, { url: '404.html' }) |
||||
}, 60000) |
||||
|
||||
resolver.resolve(name, provider).then(ipfsHash => { |
||||
clearTimeout(clearTime) |
||||
let url = 'https://ipfs.infura.io/ipfs/' + ipfsHash |
||||
return fetch(url, { method: 'HEAD' }).then(response => response.status).then(statusCode => { |
||||
if (statusCode !== 200) return extension.tabs.update(tab.id, { url: '404.html' }) |
||||
extension.tabs.update(tab.id, { url: url }) |
||||
}) |
||||
.catch(err => { |
||||
url = 'https://ipfs.infura.io/ipfs/' + ipfsHash |
||||
extension.tabs.update(tab.id, {url: url}) |
||||
return err |
||||
}) |
||||
}) |
||||
.catch(err => { |
||||
clearTimeout(clearTime) |
||||
const url = err === 'unsupport' ? 'unsupport' : 'error' |
||||
extension.tabs.update(tab.id, {url: `${url}.html?name=${name}`}) |
||||
}) |
||||
}) |
||||
return { cancel: true } |
||||
} |
||||
|
||||
extension.webRequest.onErrorOccurred.addListener(ipfsContent, {urls: ['*://*.eth/'], types: ['main_frame']}) |
||||
|
||||
return { |
||||
remove () { |
||||
extension.webRequest.onErrorOccurred.removeListener(ipfsContent) |
||||
}, |
||||
} |
||||
} |
@ -1,71 +0,0 @@ |
||||
const namehash = require('eth-ens-namehash') |
||||
const multihash = require('multihashes') |
||||
const HttpProvider = require('ethjs-provider-http') |
||||
const Eth = require('ethjs-query') |
||||
const EthContract = require('ethjs-contract') |
||||
const registrarAbi = require('./contracts/registrar') |
||||
const resolverAbi = require('./contracts/resolver') |
||||
|
||||
function ens (name, provider) { |
||||
const eth = new Eth(new HttpProvider(getProvider(provider.type))) |
||||
const hash = namehash.hash(name) |
||||
const contract = new EthContract(eth) |
||||
const Registrar = contract(registrarAbi).at(getRegistrar(provider.type)) |
||||
return new Promise((resolve, reject) => { |
||||
if (provider.type === 'mainnet' || provider.type === 'ropsten') { |
||||
Registrar.resolver(hash).then((address) => { |
||||
if (address === '0x0000000000000000000000000000000000000000') { |
||||
reject(null) |
||||
} else { |
||||
const Resolver = contract(resolverAbi).at(address['0']) |
||||
return Resolver.content(hash) |
||||
} |
||||
}).then((contentHash) => { |
||||
if (contentHash['0'] === '0x0000000000000000000000000000000000000000000000000000000000000000') reject(null) |
||||
if (contentHash.ret !== '0x') { |
||||
const hex = contentHash['0'].substring(2) |
||||
const buf = multihash.fromHexString(hex) |
||||
resolve(multihash.toB58String(multihash.encode(buf, 'sha2-256'))) |
||||
} else { |
||||
reject(null) |
||||
} |
||||
}) |
||||
} else { |
||||
return reject('unsupport') |
||||
} |
||||
}) |
||||
} |
||||
|
||||
function getProvider (type) { |
||||
switch (type) { |
||||
case 'mainnet': |
||||
return 'https://mainnet.infura.io/' |
||||
case 'ropsten': |
||||
return 'https://ropsten.infura.io/' |
||||
default: |
||||
return 'http://localhost:8545/' |
||||
} |
||||
} |
||||
|
||||
function getRegistrar (type) { |
||||
switch (type) { |
||||
case 'mainnet': |
||||
return '0x314159265dd8dbb310642f98f50c066173c1259b' |
||||
case 'ropsten': |
||||
return '0x112234455c3a32fd11230c42e7bccd4a84e02010' |
||||
default: |
||||
return '0x0000000000000000000000000000000000000000' |
||||
} |
||||
} |
||||
|
||||
module.exports.resolve = function (name, provider) { |
||||
const path = name.split('.') |
||||
const topLevelDomain = path[path.length - 1] |
||||
if (topLevelDomain === 'eth' || topLevelDomain === 'test') { |
||||
return ens(name, provider) |
||||
} else { |
||||
return new Promise((resolve, reject) => { |
||||
reject(null) |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,36 @@ |
||||
module.exports = setupFetchDebugging |
||||
|
||||
//
|
||||
// This is a utility to help resolve cases where `window.fetch` throws a
|
||||
// `TypeError: Failed to Fetch` without any stack or context for the request
|
||||
// https://github.com/getsentry/sentry-javascript/pull/1293
|
||||
//
|
||||
|
||||
function setupFetchDebugging() { |
||||
if (!global.fetch) return |
||||
const originalFetch = global.fetch |
||||
|
||||
global.fetch = wrappedFetch |
||||
|
||||
async function wrappedFetch(...args) { |
||||
const initialStack = getCurrentStack() |
||||
try { |
||||
return await originalFetch.call(window, ...args) |
||||
} catch (err) { |
||||
if (!err.stack) { |
||||
console.warn('FetchDebugger - fetch encountered an Error without a stack', err) |
||||
console.warn('FetchDebugger - overriding stack to point of original call') |
||||
err.stack = initialStack
|
||||
} |
||||
throw err |
||||
} |
||||
} |
||||
} |
||||
|
||||
function getCurrentStack() { |
||||
try { |
||||
throw new Error('Fake error for generating stack trace') |
||||
} catch (err) { |
||||
return err.stack |
||||
} |
||||
} |
@ -1,58 +1,55 @@ |
||||
const Raven = require('raven-js') |
||||
const Sentry = require('@sentry/browser') |
||||
const METAMASK_DEBUG = process.env.METAMASK_DEBUG |
||||
const extractEthjsErrorMessage = require('./extractEthjsErrorMessage') |
||||
const PROD = 'https://3567c198f8a8412082d32655da2961d0@sentry.io/273505' |
||||
const DEV = 'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496' |
||||
const SENTRY_DSN_PROD = 'https://3567c198f8a8412082d32655da2961d0@sentry.io/273505' |
||||
const SENTRY_DSN_DEV = 'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496' |
||||
|
||||
module.exports = setupRaven |
||||
module.exports = setupSentry |
||||
|
||||
// Setup raven / sentry remote error reporting
|
||||
function setupRaven (opts) { |
||||
const { release } = opts |
||||
let ravenTarget |
||||
// Setup sentry remote error reporting
|
||||
function setupSentry (opts) { |
||||
const { release, getState } = opts |
||||
let sentryTarget |
||||
// detect brave
|
||||
const isBrave = Boolean(window.chrome.ipcRenderer) |
||||
|
||||
if (METAMASK_DEBUG) { |
||||
console.log('Setting up Sentry Remote Error Reporting: DEV') |
||||
ravenTarget = DEV |
||||
console.log('Setting up Sentry Remote Error Reporting: SENTRY_DSN_DEV') |
||||
sentryTarget = SENTRY_DSN_DEV |
||||
} else { |
||||
console.log('Setting up Sentry Remote Error Reporting: PROD') |
||||
ravenTarget = PROD |
||||
console.log('Setting up Sentry Remote Error Reporting: SENTRY_DSN_PROD') |
||||
sentryTarget = SENTRY_DSN_PROD |
||||
} |
||||
|
||||
const client = Raven.config(ravenTarget, { |
||||
Sentry.init({ |
||||
dsn: sentryTarget, |
||||
debug: METAMASK_DEBUG, |
||||
release, |
||||
transport: function (opts) { |
||||
opts.data.extra.isBrave = isBrave |
||||
const report = opts.data |
||||
beforeSend: (report) => rewriteReport(report), |
||||
}) |
||||
|
||||
Sentry.configureScope(scope => { |
||||
scope.setExtra('isBrave', isBrave) |
||||
}) |
||||
|
||||
function rewriteReport(report) { |
||||
try { |
||||
// handle error-like non-error exceptions
|
||||
rewriteErrorLikeExceptions(report) |
||||
// simplify certain complex error messages (e.g. Ethjs)
|
||||
simplifyErrorMessages(report) |
||||
// modify report urls
|
||||
rewriteReportUrls(report) |
||||
// append app state
|
||||
if (getState) { |
||||
const appState = getState() |
||||
report.extra.appState = appState |
||||
} |
||||
} catch (err) { |
||||
console.warn(err) |
||||
} |
||||
// make request normally
|
||||
client._makeRequest(opts) |
||||
}, |
||||
}) |
||||
client.install() |
||||
|
||||
return Raven |
||||
return report |
||||
} |
||||
|
||||
function rewriteErrorLikeExceptions (report) { |
||||
// handle errors that lost their error-ness in serialization (e.g. dnode)
|
||||
rewriteErrorMessages(report, (errorMessage) => { |
||||
if (!errorMessage.includes('Non-Error exception captured with keys:')) return errorMessage |
||||
if (!(report.extra && report.extra.__serialized__ && report.extra.__serialized__.message)) return errorMessage |
||||
return `Non-Error Exception: ${report.extra.__serialized__.message}` |
||||
}) |
||||
return Sentry |
||||
} |
||||
|
||||
function simplifyErrorMessages (report) { |
@ -0,0 +1,64 @@ |
||||
import PropTypes from 'prop-types' |
||||
import React, { Component } from 'react' |
||||
import { approveProviderRequest, rejectProviderRequest } from '../../ui/app/actions' |
||||
import { connect } from 'react-redux' |
||||
class ProviderApproval extends Component { |
||||
render () { |
||||
const { approveProviderRequest, origin, rejectProviderRequest } = this.props |
||||
return ( |
||||
<div className="flex-column flex-grow"> |
||||
<style dangerouslySetInnerHTML={{__html: ` |
||||
.provider_approval_actions { |
||||
display: flex; |
||||
justify-content: flex-end; |
||||
margin: 14px 25px; |
||||
} |
||||
.provider_approval_actions button { |
||||
margin-left: 10px; |
||||
text-transform: uppercase; |
||||
} |
||||
.provider_approval_content { |
||||
padding: 0 25px; |
||||
} |
||||
.provider_approval_origin { |
||||
font-weight: bold; |
||||
margin: 14px 0; |
||||
} |
||||
`}} />
|
||||
<div className="section-title flex-row flex-center"> |
||||
<i |
||||
className="fa fa-arrow-left fa-lg cursor-pointer" |
||||
onClick={() => { rejectProviderRequest(origin) }} /> |
||||
<h2 className="page-subtitle">Web3 API Request</h2> |
||||
</div> |
||||
<div className="provider_approval_content"> |
||||
{"The domain listed below is requesting access to the Ethereum blockchain and to view your current account. Always double check that you're on the correct site before approving access."} |
||||
<div className="provider_approval_origin">{origin}</div> |
||||
</div> |
||||
<div className="provider_approval_actions"> |
||||
<button |
||||
className="btn-green" |
||||
onClick={() => { approveProviderRequest(origin) }}>APPROVE</button> |
||||
<button |
||||
className="cancel btn-red" |
||||
onClick={() => { rejectProviderRequest(origin) }}>REJECT</button> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
} |
||||
|
||||
ProviderApproval.propTypes = { |
||||
approveProviderRequest: PropTypes.func, |
||||
origin: PropTypes.string, |
||||
rejectProviderRequest: PropTypes.func, |
||||
} |
||||
|
||||
function mapDispatchToProps (dispatch) { |
||||
return { |
||||
approveProviderRequest: origin => dispatch(approveProviderRequest(origin)), |
||||
rejectProviderRequest: origin => dispatch(rejectProviderRequest(origin)), |
||||
} |
||||
} |
||||
|
||||
module.exports = connect(null, mapDispatchToProps)(ProviderApproval) |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,360 @@ |
||||
const path = require('path') |
||||
const assert = require('assert') |
||||
const webdriver = require('selenium-webdriver') |
||||
const { By, until } = webdriver |
||||
const { |
||||
delay, |
||||
buildChromeWebDriver, |
||||
buildFirefoxWebdriver, |
||||
installWebExt, |
||||
getExtensionIdChrome, |
||||
getExtensionIdFirefox, |
||||
} = require('../func') |
||||
const { |
||||
checkBrowserForConsoleErrors, |
||||
closeAllWindowHandlesExcept, |
||||
findElement, |
||||
findElements, |
||||
loadExtension, |
||||
verboseReportOnFailure, |
||||
} = require('./helpers') |
||||
|
||||
describe('MetaMask', function () { |
||||
let extensionId |
||||
let driver |
||||
|
||||
const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent' |
||||
const tinyDelayMs = 200 |
||||
const regularDelayMs = tinyDelayMs * 2 |
||||
const largeDelayMs = regularDelayMs * 2 |
||||
|
||||
this.timeout(0) |
||||
this.bail(true) |
||||
|
||||
before(async function () { |
||||
switch (process.env.SELENIUM_BROWSER) { |
||||
case 'chrome': { |
||||
const extPath = path.resolve('dist/chrome') |
||||
driver = buildChromeWebDriver(extPath, { responsive: true }) |
||||
extensionId = await getExtensionIdChrome(driver) |
||||
await driver.get(`chrome-extension://${extensionId}/popup.html`) |
||||
break |
||||
} |
||||
case 'firefox': { |
||||
const extPath = path.resolve('dist/firefox') |
||||
driver = buildFirefoxWebdriver({ responsive: true }) |
||||
await installWebExt(driver, extPath) |
||||
await delay(700) |
||||
extensionId = await getExtensionIdFirefox(driver) |
||||
await driver.get(`moz-extension://${extensionId}/popup.html`) |
||||
} |
||||
} |
||||
}) |
||||
|
||||
afterEach(async function () { |
||||
if (process.env.SELENIUM_BROWSER === 'chrome') { |
||||
const errors = await checkBrowserForConsoleErrors(driver) |
||||
if (errors.length) { |
||||
const errorReports = errors.map(err => err.message) |
||||
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` |
||||
console.error(new Error(errorMessage)) |
||||
} |
||||
} |
||||
if (this.currentTest.state === 'failed') { |
||||
await verboseReportOnFailure(driver, this.currentTest) |
||||
} |
||||
}) |
||||
|
||||
after(async function () { |
||||
await driver.quit() |
||||
}) |
||||
|
||||
describe('New UI setup', async function () { |
||||
it('switches to first tab', async function () { |
||||
await delay(tinyDelayMs) |
||||
const [firstTab] = await driver.getAllWindowHandles() |
||||
await driver.switchTo().window(firstTab) |
||||
await delay(regularDelayMs) |
||||
}) |
||||
|
||||
it('selects the new UI option', async () => { |
||||
try { |
||||
const overlay = await findElement(driver, By.css('.full-flex-height')) |
||||
await driver.wait(until.stalenessOf(overlay)) |
||||
} catch (e) {} |
||||
|
||||
let button |
||||
try { |
||||
button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]")) |
||||
} catch (e) { |
||||
await loadExtension(driver, extensionId) |
||||
await delay(largeDelayMs) |
||||
button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]")) |
||||
} |
||||
await button.click() |
||||
await delay(regularDelayMs) |
||||
|
||||
// Close all other tabs
|
||||
const [tab0, tab1, tab2] = await driver.getAllWindowHandles() |
||||
await driver.switchTo().window(tab0) |
||||
await delay(tinyDelayMs) |
||||
|
||||
let selectedUrl = await driver.getCurrentUrl() |
||||
await delay(tinyDelayMs) |
||||
if (tab0 && selectedUrl.match(/popup.html/)) { |
||||
await closeAllWindowHandlesExcept(driver, tab0) |
||||
} else if (tab1) { |
||||
await driver.switchTo().window(tab1) |
||||
selectedUrl = await driver.getCurrentUrl() |
||||
await delay(tinyDelayMs) |
||||
if (selectedUrl.match(/popup.html/)) { |
||||
await closeAllWindowHandlesExcept(driver, tab1) |
||||
} else if (tab2) { |
||||
await driver.switchTo().window(tab2) |
||||
selectedUrl = await driver.getCurrentUrl() |
||||
selectedUrl.match(/popup.html/) && await closeAllWindowHandlesExcept(driver, tab2) |
||||
} |
||||
} else { |
||||
throw new Error('popup.html not found') |
||||
} |
||||
await delay(regularDelayMs) |
||||
const [appTab] = await driver.getAllWindowHandles() |
||||
await driver.switchTo().window(appTab) |
||||
await delay(tinyDelayMs) |
||||
|
||||
await loadExtension(driver, extensionId) |
||||
await delay(regularDelayMs) |
||||
|
||||
const continueBtn = await findElement(driver, By.css('.welcome-screen__button')) |
||||
await continueBtn.click() |
||||
await delay(regularDelayMs) |
||||
}) |
||||
}) |
||||
|
||||
describe('Going through the first time flow', () => { |
||||
it('accepts a secure password', async () => { |
||||
const passwordBox = await findElement(driver, By.css('.create-password #create-password')) |
||||
const passwordBoxConfirm = await findElement(driver, By.css('.create-password #confirm-password')) |
||||
const button = await findElement(driver, By.css('.create-password button')) |
||||
|
||||
await passwordBox.sendKeys('correct horse battery staple') |
||||
await passwordBoxConfirm.sendKeys('correct horse battery staple') |
||||
await button.click() |
||||
await delay(regularDelayMs) |
||||
}) |
||||
|
||||
it('clicks through the unique image screen', async () => { |
||||
const nextScreen = await findElement(driver, By.css('.unique-image button')) |
||||
await nextScreen.click() |
||||
await delay(regularDelayMs) |
||||
}) |
||||
|
||||
it('clicks through the ToS', async () => { |
||||
// terms of use
|
||||
const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled() |
||||
assert.equal(canClickThrough, false, 'disabled continue button') |
||||
const bottomOfTos = await findElement(driver, By.linkText('Attributions')) |
||||
await driver.executeScript('arguments[0].scrollIntoView(true)', bottomOfTos) |
||||
await delay(regularDelayMs) |
||||
const acceptTos = await findElement(driver, By.css('.tou button')) |
||||
driver.wait(until.elementIsEnabled(acceptTos)) |
||||
await acceptTos.click() |
||||
await delay(regularDelayMs) |
||||
}) |
||||
|
||||
it('clicks through the privacy notice', async () => { |
||||
// privacy notice
|
||||
const nextScreen = await findElement(driver, By.css('.tou button')) |
||||
await nextScreen.click() |
||||
await delay(regularDelayMs) |
||||
}) |
||||
|
||||
it('clicks through the phishing notice', async () => { |
||||
// phishing notice
|
||||
const noticeElement = await driver.findElement(By.css('.markdown')) |
||||
await driver.executeScript('arguments[0].scrollTop = arguments[0].scrollHeight', noticeElement) |
||||
await delay(regularDelayMs) |
||||
const nextScreen = await findElement(driver, By.css('.tou button')) |
||||
await nextScreen.click() |
||||
await delay(regularDelayMs) |
||||
}) |
||||
|
||||
let seedPhrase |
||||
|
||||
it('reveals the seed phrase', async () => { |
||||
const byRevealButton = By.css('.backup-phrase__secret-blocker .backup-phrase__reveal-button') |
||||
await driver.wait(until.elementLocated(byRevealButton, 10000)) |
||||
const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000) |
||||
await revealSeedPhraseButton.click() |
||||
await delay(regularDelayMs) |
||||
|
||||
seedPhrase = await driver.findElement(By.css('.backup-phrase__secret-words')).getText() |
||||
assert.equal(seedPhrase.split(' ').length, 12) |
||||
await delay(regularDelayMs) |
||||
|
||||
const nextScreen = await findElement(driver, By.css('.backup-phrase button')) |
||||
await nextScreen.click() |
||||
await delay(regularDelayMs) |
||||
}) |
||||
|
||||
async function clickWordAndWait (word) { |
||||
const xpathClass = 'backup-phrase__confirm-seed-option backup-phrase__confirm-seed-option--unselected' |
||||
const xpath = `//button[@class='${xpathClass}' and contains(text(), '${word}')]` |
||||
const word0 = await findElement(driver, By.xpath(xpath), 10000) |
||||
|
||||
await word0.click() |
||||
await delay(tinyDelayMs) |
||||
} |
||||
|
||||
async function retypeSeedPhrase (words, wasReloaded, count = 0) { |
||||
try { |
||||
if (wasReloaded) { |
||||
const byRevealButton = By.css('.backup-phrase__secret-blocker .backup-phrase__reveal-button') |
||||
await driver.wait(until.elementLocated(byRevealButton, 10000)) |
||||
const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000) |
||||
await revealSeedPhraseButton.click() |
||||
await delay(regularDelayMs) |
||||
|
||||
const nextScreen = await findElement(driver, By.css('.backup-phrase button')) |
||||
await nextScreen.click() |
||||
await delay(regularDelayMs) |
||||
} |
||||
|
||||
for (let i = 0; i < 12; i++) { |
||||
await clickWordAndWait(words[i]) |
||||
} |
||||
} catch (e) { |
||||
if (count > 2) { |
||||
throw e |
||||
} else { |
||||
await loadExtension(driver, extensionId) |
||||
await retypeSeedPhrase(words, true, count + 1) |
||||
} |
||||
} |
||||
} |
||||
|
||||
it('can retype the seed phrase', async () => { |
||||
const words = seedPhrase.split(' ') |
||||
|
||||
await retypeSeedPhrase(words) |
||||
|
||||
const confirm = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`)) |
||||
await confirm.click() |
||||
await delay(regularDelayMs) |
||||
}) |
||||
|
||||
it('clicks through the deposit modal', async () => { |
||||
const byBuyModal = By.css('span .modal') |
||||
const buyModal = await driver.wait(until.elementLocated(byBuyModal)) |
||||
const closeModal = await findElement(driver, By.css('.page-container__header-close')) |
||||
await closeModal.click() |
||||
await driver.wait(until.stalenessOf(buyModal)) |
||||
await delay(regularDelayMs) |
||||
}) |
||||
}) |
||||
|
||||
describe('Show account information', () => { |
||||
it('show account details dropdown menu', async () => { |
||||
await driver.findElement(By.css('div.menu-bar__open-in-browser')).click() |
||||
const options = await driver.findElements(By.css('div.menu.account-details-dropdown div.menu__item')) |
||||
assert.equal(options.length, 3) // HD Wallet type does not have to show the Remove Account option
|
||||
await delay(regularDelayMs) |
||||
}) |
||||
}) |
||||
|
||||
describe('Import seed phrase', () => { |
||||
it('logs out of the vault', async () => { |
||||
await driver.findElement(By.css('.account-menu__icon')).click() |
||||
await delay(regularDelayMs) |
||||
|
||||
const logoutButton = await findElement(driver, By.css('.account-menu__logout-button')) |
||||
assert.equal(await logoutButton.getText(), 'Log out') |
||||
await logoutButton.click() |
||||
await delay(regularDelayMs) |
||||
}) |
||||
|
||||
it('imports seed phrase', async () => { |
||||
const restoreSeedLink = await findElement(driver, By.css('.unlock-page__link--import')) |
||||
assert.equal(await restoreSeedLink.getText(), 'Import using account seed phrase') |
||||
await restoreSeedLink.click() |
||||
await delay(regularDelayMs) |
||||
|
||||
const seedTextArea = await findElement(driver, By.css('textarea')) |
||||
await seedTextArea.sendKeys(testSeedPhrase) |
||||
await delay(regularDelayMs) |
||||
|
||||
const passwordInputs = await driver.findElements(By.css('input')) |
||||
await delay(regularDelayMs) |
||||
|
||||
await passwordInputs[0].sendKeys('correct horse battery staple') |
||||
await passwordInputs[1].sendKeys('correct horse battery staple') |
||||
await driver.findElement(By.css('.first-time-flow__button')).click() |
||||
await delay(regularDelayMs) |
||||
}) |
||||
|
||||
it('switches to localhost', async () => { |
||||
const networkDropdown = await findElement(driver, By.css('.network-name')) |
||||
await networkDropdown.click() |
||||
await delay(regularDelayMs) |
||||
|
||||
const [localhost] = await findElements(driver, By.xpath(`//span[contains(text(), 'Localhost')]`)) |
||||
await localhost.click() |
||||
await delay(largeDelayMs * 2) |
||||
}) |
||||
|
||||
it('balance renders', async () => { |
||||
const balance = await findElement(driver, By.css('.transaction-view-balance__primary-balance')) |
||||
await driver.wait(until.elementTextMatches(balance, /100\s*ETH/)) |
||||
await delay(regularDelayMs) |
||||
}) |
||||
}) |
||||
|
||||
describe('Send ETH from inside MetaMask', () => { |
||||
it('starts to send a transaction', async function () { |
||||
const sendButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Send')]`)) |
||||
await sendButton.click() |
||||
await delay(regularDelayMs) |
||||
|
||||
const inputAddress = await findElement(driver, By.css('input[placeholder="Recipient Address"]')) |
||||
const inputAmount = await findElement(driver, By.css('.unit-input__input')) |
||||
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') |
||||
await inputAmount.sendKeys('1') |
||||
|
||||
const inputValue = await inputAmount.getAttribute('value') |
||||
assert.equal(inputValue, '1') |
||||
|
||||
// Set the gas limit
|
||||
const configureGas = await findElement(driver, By.css('.send-v2__gas-fee-display button')) |
||||
await configureGas.click() |
||||
await delay(regularDelayMs) |
||||
|
||||
const gasModal = await driver.findElement(By.css('span .modal')) |
||||
|
||||
const save = await findElement(driver, By.xpath(`//button[contains(text(), 'Save')]`)) |
||||
await save.click() |
||||
await driver.wait(until.stalenessOf(gasModal)) |
||||
await delay(regularDelayMs) |
||||
|
||||
// Continue to next screen
|
||||
const nextScreen = await findElement(driver, By.xpath(`//button[contains(text(), 'Next')]`)) |
||||
await nextScreen.click() |
||||
await delay(regularDelayMs) |
||||
}) |
||||
|
||||
it('confirms the transaction', async function () { |
||||
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`)) |
||||
await confirmButton.click() |
||||
await delay(largeDelayMs) |
||||
}) |
||||
|
||||
it('finds the transaction in the transactions list', async function () { |
||||
const transactions = await findElements(driver, By.css('.transaction-list-item')) |
||||
assert.equal(transactions.length, 1) |
||||
|
||||
if (process.env.SELENIUM_BROWSER !== 'firefox') { |
||||
const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary')) |
||||
await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/), 10000) |
||||
} |
||||
}) |
||||
}) |
||||
}) |
@ -1,140 +0,0 @@ |
||||
const reactTriggerChange = require('react-trigger-change') |
||||
const { |
||||
timeout, |
||||
queryAsync, |
||||
findAsync, |
||||
} = require('../../lib/util') |
||||
|
||||
QUnit.module('Add token flow') |
||||
|
||||
QUnit.test('successful add token flow', (assert) => { |
||||
const done = assert.async() |
||||
runAddTokenFlowTest(assert) |
||||
.then(done) |
||||
.catch(err => { |
||||
assert.notOk(err, `Error was thrown: ${err.stack}`) |
||||
done() |
||||
}) |
||||
}) |
||||
|
||||
async function runAddTokenFlowTest (assert, done) { |
||||
const selectState = await queryAsync($, 'select') |
||||
selectState.val('add token') |
||||
reactTriggerChange(selectState[0]) |
||||
|
||||
// Used to set values on TextField input component
|
||||
const nativeInputValueSetter = Object.getOwnPropertyDescriptor( |
||||
window.HTMLInputElement.prototype, 'value' |
||||
).set |
||||
|
||||
// Check that no tokens have been added
|
||||
assert.ok($('.token-list-item').length === 0, 'no tokens added') |
||||
|
||||
// Go to Add Token screen
|
||||
let addTokenButton = await queryAsync($, 'button.btn-primary.wallet-view__add-token-button') |
||||
assert.ok(addTokenButton[0], 'add token button present') |
||||
addTokenButton[0].click() |
||||
|
||||
// Verify Add Token screen
|
||||
let addTokenWrapper = await queryAsync($, '.page-container') |
||||
assert.ok(addTokenWrapper[0], 'add token wrapper renders') |
||||
|
||||
let addTokenTitle = await queryAsync($, '.page-container__title') |
||||
assert.equal(addTokenTitle[0].textContent, 'Add Tokens', 'add token title is correct') |
||||
|
||||
// Cancel Add Token
|
||||
const cancelAddTokenButton = await queryAsync($, 'button.btn-default.btn--large.page-container__footer-button') |
||||
assert.ok(cancelAddTokenButton[0], 'cancel add token button present') |
||||
cancelAddTokenButton.click() |
||||
|
||||
assert.ok($('.wallet-view')[0], 'cancelled and returned to account detail wallet view') |
||||
|
||||
// Return to Add Token Screen
|
||||
addTokenButton = await queryAsync($, 'button.btn-primary.wallet-view__add-token-button') |
||||
assert.ok(addTokenButton[0], 'add token button present') |
||||
addTokenButton[0].click() |
||||
|
||||
// Verify Add Token Screen
|
||||
addTokenWrapper = await queryAsync($, '.page-container') |
||||
addTokenTitle = await queryAsync($, '.page-container__title') |
||||
assert.ok(addTokenWrapper[0], 'add token wrapper renders') |
||||
assert.equal(addTokenTitle[0].textContent, 'Add Tokens', 'add token title is correct') |
||||
|
||||
// Search for token
|
||||
const searchInput = (await findAsync(addTokenWrapper, '#search-tokens'))[0] |
||||
searchInput.focus() |
||||
await timeout(1000) |
||||
nativeInputValueSetter.call(searchInput, 'a') |
||||
searchInput.dispatchEvent(new Event('input', { bubbles: true})) |
||||
|
||||
// Click token to add
|
||||
const tokenWrapper = await queryAsync($, 'div.token-list__token') |
||||
assert.ok(tokenWrapper[0], 'token found') |
||||
const tokenImageProp = tokenWrapper.find('.token-list__token-icon').css('background-image') |
||||
const tokenImageUrl = tokenImageProp.slice(5, -2) |
||||
tokenWrapper[0].click() |
||||
|
||||
// Click Next button
|
||||
const nextButton = await queryAsync($, 'button.btn-primary.btn--large') |
||||
assert.equal(nextButton[0].textContent, 'Next', 'next button rendered') |
||||
nextButton[0].click() |
||||
|
||||
// Confirm Add token
|
||||
const confirmAddToken = await queryAsync($, '.confirm-add-token') |
||||
assert.ok(confirmAddToken[0], 'confirm add token rendered') |
||||
assert.ok($('button.btn-primary.btn--large')[0], 'confirm add token button found') |
||||
$('button.btn-primary.btn--large')[0].click() |
||||
|
||||
// Verify added token image
|
||||
let heroBalance = await queryAsync($, '.transaction-view-balance__balance-container') |
||||
assert.ok(heroBalance, 'rendered hero balance') |
||||
assert.ok(tokenImageUrl.indexOf(heroBalance.find('img').attr('src')) > -1, 'token added') |
||||
|
||||
// Return to Add Token Screen
|
||||
addTokenButton = await queryAsync($, 'button.btn-primary.wallet-view__add-token-button') |
||||
assert.ok(addTokenButton[0], 'add token button present') |
||||
addTokenButton[0].click() |
||||
|
||||
addTokenWrapper = await queryAsync($, '.page-container') |
||||
const addTokenTabs = await queryAsync($, '.page-container__tab') |
||||
assert.equal(addTokenTabs.length, 2, 'expected number of tabs') |
||||
assert.equal(addTokenTabs[1].textContent, 'Custom Token', 'Custom Token tab present') |
||||
assert.ok(addTokenTabs[1], 'add custom token tab present') |
||||
addTokenTabs[1].click() |
||||
await timeout(1000) |
||||
|
||||
// Input token contract address
|
||||
const customInput = (await findAsync(addTokenWrapper, '#custom-address'))[0] |
||||
customInput.focus() |
||||
await timeout(1000) |
||||
nativeInputValueSetter.call(customInput, '0x177af043D3A1Aed7cc5f2397C70248Fc6cDC056c') |
||||
customInput.dispatchEvent(new Event('input', { bubbles: true})) |
||||
|
||||
|
||||
// Click Next button
|
||||
// nextButton = await queryAsync($, 'button.btn-primary--lg')
|
||||
// assert.equal(nextButton[0].textContent, 'Next', 'next button rendered')
|
||||
// nextButton[0].click()
|
||||
|
||||
// // Verify symbol length error since contract address won't return symbol
|
||||
const errorMessage = await queryAsync($, '#custom-symbol-helper-text') |
||||
assert.ok(errorMessage[0], 'error rendered') |
||||
|
||||
$('button.btn-default.btn--large')[0].click() |
||||
|
||||
// await timeout(100000)
|
||||
|
||||
// Confirm Add token
|
||||
// assert.equal(
|
||||
// $('.page-container__subtitle')[0].textContent,
|
||||
// 'Would you like to add these tokens?',
|
||||
// 'confirm add token rendered'
|
||||
// )
|
||||
// assert.ok($('button.btn-primary--lg')[0], 'confirm add token button found')
|
||||
// $('button.btn-primary--lg')[0].click()
|
||||
|
||||
// Verify added token image
|
||||
heroBalance = await queryAsync($, '.transaction-view-balance__balance-container') |
||||
assert.ok(heroBalance, 'rendered hero balance') |
||||
assert.ok(heroBalance.find('.identicon')[0], 'token added') |
||||
} |
@ -1,44 +0,0 @@ |
||||
const assert = require('assert') |
||||
const h = require('react-hyperscript') |
||||
const { createMockStore } = require('redux-test-utils') |
||||
const { shallowWithStore } = require('../../lib/render-helpers') |
||||
const BalanceComponent = require('../../../ui/app/components/balance-component') |
||||
const mockState = { |
||||
metamask: { |
||||
accounts: { abc: {} }, |
||||
network: 1, |
||||
selectedAddress: 'abc', |
||||
}, |
||||
} |
||||
|
||||
describe('BalanceComponent', function () { |
||||
let balanceComponent |
||||
let store |
||||
let component |
||||
beforeEach(function () { |
||||
store = createMockStore(mockState) |
||||
component = shallowWithStore(h(BalanceComponent), store) |
||||
balanceComponent = component.dive() |
||||
}) |
||||
|
||||
it('shows token balance and convert to fiat value based on conversion rate', function () { |
||||
const formattedBalance = '1.23 ETH' |
||||
|
||||
const tokenBalance = balanceComponent.instance().getTokenBalance(formattedBalance, false) |
||||
const fiatDisplayNumber = balanceComponent.instance().getFiatDisplayNumber(formattedBalance, 2) |
||||
|
||||
assert.equal('1.23 ETH', tokenBalance) |
||||
assert.equal(2.46, fiatDisplayNumber) |
||||
}) |
||||
|
||||
it('shows only the token balance when conversion rate is not available', function () { |
||||
const formattedBalance = '1.23 ETH' |
||||
|
||||
const tokenBalance = balanceComponent.instance().getTokenBalance(formattedBalance, false) |
||||
const fiatDisplayNumber = balanceComponent.instance().getFiatDisplayNumber(formattedBalance, 0) |
||||
|
||||
assert.equal('1.23 ETH', tokenBalance) |
||||
assert.equal('N/A', fiatDisplayNumber) |
||||
}) |
||||
|
||||
}) |
@ -0,0 +1,998 @@ |
||||
import assert from 'assert' |
||||
import reduceApp from '../../../../../ui/app/reducers/app' |
||||
import * as actions from '../../../../../ui/app/actions' |
||||
|
||||
describe('App State', () => { |
||||
|
||||
const metamaskState = { |
||||
metamask: { |
||||
selectedAddress: '0xAddress', |
||||
identities: { |
||||
'0xAddress': { |
||||
name: 'account 1', |
||||
address: '0xAddress', |
||||
}, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
it('App init state', () => { |
||||
const initState = reduceApp(metamaskState, {}) |
||||
|
||||
assert(initState) |
||||
}) |
||||
|
||||
it('sets networkd dropdown to true', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.NETWORK_DROPDOWN_OPEN, |
||||
}) |
||||
|
||||
assert.equal(state.networkDropdownOpen, true) |
||||
}) |
||||
|
||||
it('sets networkd dropdown to false', () => { |
||||
const dropdown = { networkDropdowopen: true } |
||||
const state = {...metamaskState, ...dropdown} |
||||
const newState = reduceApp(state, { |
||||
type: actions.NETWORK_DROPDOWN_CLOSE, |
||||
}) |
||||
|
||||
assert.equal(newState.networkDropdownOpen, false) |
||||
}) |
||||
|
||||
it('opens sidebar', () => { |
||||
const value = { |
||||
'transitionName': 'sidebar-right', |
||||
'type': 'wallet-view', |
||||
'isOpen': true, |
||||
} |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SIDEBAR_OPEN, |
||||
value, |
||||
}) |
||||
|
||||
assert.deepEqual(state.sidebar, value) |
||||
}) |
||||
|
||||
it('closes sidebar', () => { |
||||
const openSidebar = { sidebar: { isOpen: true }} |
||||
const state = {...metamaskState, ...openSidebar} |
||||
|
||||
const newState = reduceApp(state, { |
||||
type: actions.SIDEBAR_CLOSE, |
||||
}) |
||||
|
||||
assert.equal(newState.sidebar.isOpen, false) |
||||
}) |
||||
|
||||
it('opens alert', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.ALERT_OPEN, |
||||
value: 'test message', |
||||
}) |
||||
|
||||
assert.equal(state.alertOpen, true) |
||||
assert.equal(state.alertMessage, 'test message') |
||||
}) |
||||
|
||||
it('closes alert', () => { |
||||
const alert = { alertOpen: true, alertMessage: 'test message' } |
||||
const state = {...metamaskState, ...alert} |
||||
const newState = reduceApp(state, { |
||||
type: actions.ALERT_CLOSE, |
||||
}) |
||||
|
||||
assert.equal(newState.alertOpen, false) |
||||
assert.equal(newState.alertMessage, null) |
||||
}) |
||||
|
||||
it('detects qr code data', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.QR_CODE_DETECTED, |
||||
value: 'qr data', |
||||
}) |
||||
|
||||
assert.equal(state.qrCodeData, 'qr data') |
||||
}) |
||||
|
||||
it('opens modal', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.MODAL_OPEN, |
||||
payload: { |
||||
name: 'test', |
||||
}, |
||||
}) |
||||
|
||||
assert.equal(state.modal.open, true) |
||||
assert.equal(state.modal.modalState.name, 'test') |
||||
}) |
||||
|
||||
it('closes modal, but moves open modal state to previous modal state', () => { |
||||
const opensModal = { |
||||
modal: { |
||||
open: true, |
||||
modalState: { |
||||
name: 'test', |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
const state = { ...metamaskState, appState: { ...opensModal } } |
||||
const newState = reduceApp(state, { |
||||
type: actions.MODAL_CLOSE, |
||||
}) |
||||
|
||||
|
||||
assert.equal(newState.modal.open, false) |
||||
assert.equal(newState.modal.modalState.name, null) |
||||
}) |
||||
|
||||
it('tansitions forwards', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.TRANSITION_FORWARD, |
||||
}) |
||||
|
||||
assert.equal(state.transForward, true) |
||||
}) |
||||
|
||||
it('transition backwards', () => { |
||||
const transitionForwardState = { transitionForward: true } |
||||
|
||||
const state = { ...metamaskState, ...transitionForwardState } |
||||
const newState = reduceApp(state, { |
||||
type: actions.TRANSITION_BACKWARD, |
||||
}) |
||||
|
||||
assert.equal(newState.transForward, false) |
||||
}) |
||||
|
||||
it('shows create vault', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SHOW_CREATE_VAULT, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'createVault') |
||||
assert.equal(state.transForward, true) |
||||
assert.equal(state.warning, null) |
||||
}) |
||||
|
||||
it('shows restore vault', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SHOW_RESTORE_VAULT, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'restoreVault') |
||||
assert.equal(state.transForward, true) |
||||
assert.equal(state.forgottenPassword, true) |
||||
}) |
||||
|
||||
it('sets forgot password', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.FORGOT_PASSWORD, |
||||
value: true, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'restoreVault') |
||||
}) |
||||
|
||||
it('shows init menu', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SHOW_INIT_MENU, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'accountDetail') |
||||
assert.equal(state.currentView.context, '0xAddress') |
||||
}) |
||||
|
||||
it('shows config page', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SHOW_CONFIG_PAGE, |
||||
value: true, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'config') |
||||
assert.equal(state.currentView.context, '0xAddress') |
||||
assert.equal(state.transForward, true) |
||||
}) |
||||
|
||||
it('shows add token page', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SHOW_ADD_TOKEN_PAGE, |
||||
value: true, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'add-token') |
||||
assert.equal(state.currentView.context, '0xAddress') |
||||
assert.equal(state.transForward, true) |
||||
}) |
||||
|
||||
it('shows add suggested token page', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SHOW_ADD_SUGGESTED_TOKEN_PAGE, |
||||
value: true, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'add-suggested-token') |
||||
assert.equal(state.currentView.context, '0xAddress') |
||||
assert.equal(state.transForward, true) |
||||
}) |
||||
|
||||
it('shows import page', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SHOW_IMPORT_PAGE, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'import-menu') |
||||
assert.equal(state.transForward, true) |
||||
assert.equal(state.warning, null) |
||||
}) |
||||
|
||||
it('shows new account page', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SHOW_NEW_ACCOUNT_PAGE, |
||||
formToSelect: 'context', |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'new-account-page') |
||||
assert.equal(state.currentView.context, 'context') |
||||
assert.equal(state.transForward, true) |
||||
assert.equal(state.warning, null) |
||||
}) |
||||
|
||||
it('sets new account form', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SET_NEW_ACCOUNT_FORM, |
||||
formToSelect: 'context', |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'accountDetail') |
||||
assert.equal(state.currentView.context, 'context') |
||||
}) |
||||
|
||||
it('shows info page', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SHOW_INFO_PAGE, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'info') |
||||
assert.equal(state.currentView.context, '0xAddress') |
||||
assert.equal(state.transForward, true) |
||||
}) |
||||
|
||||
it('creates new vault in progress', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.CREATE_NEW_VAULT_IN_PROGRESS, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'createVault') |
||||
assert.equal(state.currentView.inProgress, true) |
||||
assert.equal(state.transForward, true) |
||||
assert.equal(state.isLoading, true) |
||||
}) |
||||
|
||||
it('shows new vault seed', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SHOW_NEW_VAULT_SEED, |
||||
value: 'test seed words', |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'createVaultComplete') |
||||
assert.equal(state.currentView.seedWords, 'test seed words') |
||||
assert.equal(state.transForward, true) |
||||
assert.equal(state.isLoading, false) |
||||
}) |
||||
|
||||
it('shows new account screen', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.NEW_ACCOUNT_SCREEN, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'new-account') |
||||
assert.equal(state.currentView.context, '0xAddress') |
||||
assert.equal(state.transForward, true) |
||||
}) |
||||
|
||||
it('shows send page', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SHOW_SEND_PAGE, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'sendTransaction') |
||||
assert.equal(state.currentView.context, '0xAddress') |
||||
assert.equal(state.transForward, true) |
||||
assert.equal(state.warning, null) |
||||
}) |
||||
|
||||
it('shows send token page', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SHOW_SEND_TOKEN_PAGE, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'sendToken') |
||||
assert.equal(state.currentView.context, '0xAddress') |
||||
assert.equal(state.transForward, true) |
||||
assert.equal(state.warning, null) |
||||
}) |
||||
|
||||
it('shows new keychain', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SHOW_NEW_KEYCHAIN, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'newKeychain') |
||||
assert.equal(state.currentView.context, '0xAddress') |
||||
assert.equal(state.transForward, true) |
||||
}) |
||||
|
||||
it('unlocks Metamask', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.UNLOCK_METAMASK, |
||||
}) |
||||
|
||||
assert.equal(state.forgottenPassword, null) |
||||
assert.deepEqual(state.detailView, {}) |
||||
assert.equal(state.transForward, true) |
||||
assert.equal(state.warning, null) |
||||
}) |
||||
|
||||
it('locks Metamask', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.LOCK_METAMASK, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'accountDetail') |
||||
assert.equal(state.currentView.context, '0xAddress') |
||||
assert.equal(state.transForward, false) |
||||
assert.equal(state.warning, null) |
||||
}) |
||||
|
||||
it('goes back to init menu', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.BACK_TO_INIT_MENU, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'InitMenu') |
||||
assert.equal(state.transForward, false) |
||||
assert.equal(state.warning, null) |
||||
assert.equal(state.forgottenPassword, true) |
||||
}) |
||||
|
||||
it('goes back to unlock view', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.BACK_TO_UNLOCK_VIEW, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'UnlockScreen') |
||||
assert.equal(state.transForward, true) |
||||
assert.equal(state.warning, null) |
||||
assert.equal(state.forgottenPassword, false) |
||||
}) |
||||
|
||||
it('reveals seed words', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.REVEAL_SEED_CONFIRMATION, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'reveal-seed-conf') |
||||
assert.equal(state.transForward, true) |
||||
assert.equal(state.warning, null) |
||||
}) |
||||
|
||||
it('sets selected account', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SET_SELECTED_ACCOUNT, |
||||
value: 'active address', |
||||
}) |
||||
|
||||
assert.equal(state.activeAddress, 'active address') |
||||
}) |
||||
|
||||
it('goes home', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.GO_HOME, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'accountDetail') |
||||
assert.equal(state.accountDetail.subview, 'transactions') |
||||
assert.equal(state.accountDetail.accountExport, 'none') |
||||
assert.equal(state.accountDetail.privateKey, '') |
||||
assert.equal(state.transForward, false) |
||||
assert.equal(state.warning, null) |
||||
|
||||
}) |
||||
|
||||
it('shows account detail', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SHOW_ACCOUNT_DETAIL, |
||||
value: 'context address', |
||||
}) |
||||
assert.equal(state.forgottenPassword, null) // default
|
||||
assert.equal(state.currentView.name, 'accountDetail') |
||||
assert.equal(state.currentView.context, 'context address') |
||||
assert.equal(state.accountDetail.subview, 'transactions') // default
|
||||
assert.equal(state.accountDetail.accountExport, 'none') // default
|
||||
assert.equal(state.accountDetail.privateKey, '') // default
|
||||
assert.equal(state.transForward, false) |
||||
|
||||
}) |
||||
|
||||
it('goes back to account detail', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.BACK_TO_ACCOUNT_DETAIL, |
||||
value: 'context address', |
||||
}) |
||||
assert.equal(state.forgottenPassword, null) // default
|
||||
assert.equal(state.currentView.name, 'accountDetail') |
||||
assert.equal(state.currentView.context, 'context address') |
||||
assert.equal(state.accountDetail.subview, 'transactions') // default
|
||||
assert.equal(state.accountDetail.accountExport, 'none') // default
|
||||
assert.equal(state.accountDetail.privateKey, '') // default
|
||||
assert.equal(state.transForward, false) |
||||
|
||||
}) |
||||
|
||||
it('shoes account page', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SHOW_ACCOUNTS_PAGE, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'accounts') |
||||
assert.equal(state.currentView.seedWords, undefined) |
||||
assert.equal(state.transForward, true) |
||||
assert.equal(state.isLoading, false) |
||||
assert.equal(state.warning, null) |
||||
assert.equal(state.scrollToBottom, false) |
||||
assert.equal(state.forgottenPassword, false) |
||||
}) |
||||
|
||||
it('shows notice', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SHOW_NOTICE, |
||||
}) |
||||
|
||||
assert.equal(state.transForward, true) |
||||
assert.equal(state.isLoading, false) |
||||
}) |
||||
|
||||
it('reveals account', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.REVEAL_ACCOUNT, |
||||
}) |
||||
assert.equal(state.scrollToBottom, true) |
||||
}) |
||||
|
||||
it('shows confirm tx page', () => { |
||||
const txs = { |
||||
unapprovedTxs: { |
||||
1: { |
||||
id: 1, |
||||
}, |
||||
2: { |
||||
id: 2, |
||||
}, |
||||
}, |
||||
} |
||||
const oldState = { |
||||
metamask: {...metamaskState.metamask, ...txs}, |
||||
} |
||||
const state = reduceApp(oldState, { |
||||
type: actions.SHOW_CONF_TX_PAGE, |
||||
id: 2, |
||||
transForward: false, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'confTx') |
||||
assert.equal(state.currentView.context, 1) |
||||
assert.equal(state.transForward, false) |
||||
assert.equal(state.warning, null) |
||||
assert.equal(state.isLoading, false) |
||||
|
||||
}) |
||||
|
||||
it('shows confirm msg page', () => { |
||||
const msgs = { |
||||
unapprovedMsgs: { |
||||
1: { |
||||
id: 1, |
||||
}, |
||||
2: { |
||||
id: 2, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
const oldState = { |
||||
metamask: {...metamaskState, ...msgs}, |
||||
} |
||||
|
||||
const state = reduceApp(oldState, { |
||||
type: actions.SHOW_CONF_MSG_PAGE, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'confTx') |
||||
assert.equal(state.currentView.context, 0) |
||||
assert.equal(state.transForward, true) |
||||
assert.equal(state.warning, null) |
||||
assert.equal(state.isLoading, false) |
||||
|
||||
}) |
||||
|
||||
it('completes tx continues to show pending txs current view context', () => { |
||||
const txs = { |
||||
unapprovedTxs: { |
||||
1: { |
||||
id: 1, |
||||
}, |
||||
2: { |
||||
id: 2, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
const oldState = { |
||||
metamask: {...metamaskState, ...txs}, |
||||
} |
||||
|
||||
const state = reduceApp(oldState, { |
||||
type: actions.COMPLETED_TX, |
||||
value: 1, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'confTx') |
||||
assert.equal(state.currentView.context, 0) |
||||
assert.equal(state.transForward, false) |
||||
assert.equal(state.warning, null) |
||||
}) |
||||
|
||||
it('returns to account detail page when no unconf actions completed tx', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.COMPLETED_TX, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'accountDetail') |
||||
assert.equal(state.currentView.context, '0xAddress') |
||||
assert.equal(state.transForward, false) |
||||
assert.equal(state.warning, null) |
||||
assert.equal(state.accountDetail.subview, 'transactions') |
||||
|
||||
}) |
||||
|
||||
it('proceeds to change current view context in confTx', () => { |
||||
|
||||
const oldState = { |
||||
metamask: {metamaskState}, |
||||
appState: {currentView: {context: 0}}, |
||||
} |
||||
|
||||
const state = reduceApp(oldState, { |
||||
type: actions.NEXT_TX, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'confTx') |
||||
assert.equal(state.currentView.context, 1) |
||||
assert.equal(state.warning, null) |
||||
}) |
||||
|
||||
it('views pending tx', () => { |
||||
const txs = { |
||||
unapprovedTxs: { |
||||
1: { |
||||
id: 1, |
||||
}, |
||||
2: { |
||||
id: 2, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
|
||||
const oldState = { |
||||
metamask: {...metamaskState, ...txs}, |
||||
} |
||||
|
||||
const state = reduceApp(oldState, { |
||||
type: actions.VIEW_PENDING_TX, |
||||
value: 2, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'confTx') |
||||
assert.equal(state.currentView.context, 1) |
||||
assert.equal(state.warning, null) |
||||
}) |
||||
|
||||
it('views previous tx', () => { |
||||
const txs = { |
||||
unapprovedTxs: { |
||||
1: { |
||||
id: 1, |
||||
}, |
||||
2: { |
||||
id: 2, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
|
||||
const oldState = { |
||||
metamask: {...metamaskState, ...txs}, |
||||
} |
||||
|
||||
const state = reduceApp(oldState, { |
||||
type: actions.VIEW_PENDING_TX, |
||||
value: 2, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'confTx') |
||||
assert.equal(state.currentView.context, 1) |
||||
assert.equal(state.warning, null) |
||||
}) |
||||
|
||||
it('sets error message in confTx view', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.TRANSACTION_ERROR, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'confTx') |
||||
assert.equal(state.currentView.errorMessage, 'There was a problem submitting this transaction.') |
||||
}) |
||||
|
||||
it('sets default warning when unlock fails', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.UNLOCK_FAILED, |
||||
}) |
||||
|
||||
assert.equal(state.warning, 'Incorrect password. Try again.') |
||||
}) |
||||
|
||||
it('sets default warning when unlock fails', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.UNLOCK_FAILED, |
||||
value: 'errors', |
||||
}) |
||||
|
||||
assert.equal(state.warning, 'errors') |
||||
}) |
||||
|
||||
it('sets warning to empty string when unlock succeeds', () => { |
||||
const errorState = { warning: 'errors' } |
||||
const oldState = {...metamaskState, ...errorState} |
||||
const state = reduceApp(oldState, { |
||||
type: actions.UNLOCK_SUCCEEDED, |
||||
}) |
||||
|
||||
assert.equal(state.warning, '') |
||||
}) |
||||
|
||||
it('sets hardware wallet default hd path', () => { |
||||
const hdPaths = { |
||||
trezor: "m/44'/60'/0'/0", |
||||
ledger: "m/44'/60'/0'", |
||||
} |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SET_HARDWARE_WALLET_DEFAULT_HD_PATH, |
||||
value: { |
||||
device: 'ledger', |
||||
path: "m/44'/60'/0'", |
||||
}, |
||||
}) |
||||
|
||||
assert.deepEqual(state.defaultHdPaths, hdPaths) |
||||
}) |
||||
|
||||
it('shows loading message', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SHOW_LOADING, |
||||
value: 'loading', |
||||
}) |
||||
|
||||
assert.equal(state.isLoading, true) |
||||
assert.equal(state.loadingMessage, 'loading') |
||||
}) |
||||
|
||||
it('hides loading message', () => { |
||||
const loadingState = { isLoading: true} |
||||
const oldState = {...metamaskState, ...loadingState} |
||||
|
||||
const state = reduceApp(oldState, { |
||||
type: actions.HIDE_LOADING, |
||||
}) |
||||
|
||||
assert.equal(state.isLoading, false) |
||||
}) |
||||
|
||||
it('shows sub loading indicator', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SHOW_SUB_LOADING_INDICATION, |
||||
}) |
||||
|
||||
assert.equal(state.isSubLoading, true) |
||||
}) |
||||
|
||||
it('hides sub loading indicator', () => { |
||||
const oldState = {...metamaskState, ...oldState} |
||||
const state = reduceApp(oldState, { |
||||
type: actions.HIDE_SUB_LOADING_INDICATION, |
||||
}) |
||||
|
||||
assert.equal(state.isSubLoading, false) |
||||
}) |
||||
|
||||
it('displays warning', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.DISPLAY_WARNING, |
||||
value: 'warning', |
||||
}) |
||||
|
||||
assert.equal(state.isLoading, false) |
||||
assert.equal(state.warning, 'warning') |
||||
}) |
||||
|
||||
it('hides warning', () => { |
||||
const displayWarningState = { warning: 'warning'} |
||||
const oldState = {...metamaskState, ...displayWarningState} |
||||
const state = reduceApp(oldState, { |
||||
type: actions.HIDE_WARNING, |
||||
}) |
||||
|
||||
assert.equal(state.warning, undefined) |
||||
}) |
||||
|
||||
it('request to display account export', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.REQUEST_ACCOUNT_EXPORT, |
||||
}) |
||||
|
||||
assert.equal(state.transForward, true) |
||||
assert.equal(state.accountDetail.subview, 'export') |
||||
assert.equal(state.accountDetail.accountExport, 'requested') |
||||
}) |
||||
|
||||
it('completes account export', () => { |
||||
const requestAccountExportState = { |
||||
accountDetail: { |
||||
subview: 'something', |
||||
accountExport: 'progress', |
||||
}, |
||||
} |
||||
const oldState = {...metamaskState, ...requestAccountExportState} |
||||
const state = reduceApp(oldState, { |
||||
type: actions.EXPORT_ACCOUNT, |
||||
}) |
||||
|
||||
assert.equal(state.accountDetail.subview, 'export') |
||||
assert.equal(state.accountDetail.accountExport, 'completed') |
||||
}) |
||||
|
||||
it('shows private key', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SHOW_PRIVATE_KEY, |
||||
value: 'private key', |
||||
}) |
||||
|
||||
assert.equal(state.accountDetail.subview, 'export') |
||||
assert.equal(state.accountDetail.accountExport, 'completed') |
||||
assert.equal(state.accountDetail.privateKey, 'private key') |
||||
}) |
||||
|
||||
it('shows buy eth view', () => { |
||||
|
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.BUY_ETH_VIEW, |
||||
value: '0xAddress', |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'buyEth') |
||||
assert.equal(state.currentView.context, 'accountDetail') |
||||
assert.equal(state.identity.address, '0xAddress') |
||||
assert.equal(state.buyView.subview, 'Coinbase') |
||||
assert.equal(state.buyView.amount, '15.00') |
||||
assert.equal(state.buyView.buyAddress, '0xAddress') |
||||
assert.equal(state.buyView.formView.coinbase, true) |
||||
assert.equal(state.buyView.formView.shapeshift, false) |
||||
}) |
||||
|
||||
it('shows onboarding subview to buy eth', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.ONBOARDING_BUY_ETH_VIEW, |
||||
value: '0xAddress', |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'onboardingBuyEth') |
||||
assert.equal(state.currentView.context, 'accountDetail') |
||||
assert.equal(state.identity.address, '0xAddress') |
||||
}) |
||||
|
||||
it('shows coinbase subview', () => { |
||||
const appState = { |
||||
appState: { |
||||
buyView: { |
||||
buyAddress: '0xAddress', |
||||
amount: '12.00', |
||||
}, |
||||
}, |
||||
} |
||||
const oldState = {...metamaskState, ...appState} |
||||
const state = reduceApp(oldState, { |
||||
type: actions.COINBASE_SUBVIEW, |
||||
}) |
||||
|
||||
assert.equal(state.buyView.subview, 'Coinbase') |
||||
assert.equal(state.buyView.formView.coinbase, true) |
||||
assert.equal(state.buyView.buyAddress, '0xAddress') |
||||
assert.equal(state.buyView.amount, '12.00') |
||||
}) |
||||
|
||||
it('shows shapeshift subview', () => { |
||||
const appState = { |
||||
appState: { |
||||
buyView: { |
||||
buyAddress: '0xAddress', |
||||
amount: '12.00', |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
const marketinfo = { |
||||
pair: 'BTC_ETH', |
||||
rate: 28.91191106, |
||||
minerFee: 0.0022, |
||||
limit: 0.76617432, |
||||
minimum: 0.00015323, |
||||
maxLimit: 0.76617432, |
||||
} |
||||
|
||||
const coinOptions = { |
||||
BTC: { |
||||
symbol: 'BTC', |
||||
name: 'Bitcoin', |
||||
image: 'https://shapeshift.io/images/coins/bitcoin.png', |
||||
imageSmall: 'https://shapeshift.io/images/coins-sm/bitcoin.png', |
||||
status: 'available', |
||||
minerFee: 0.00025, |
||||
}, |
||||
} |
||||
|
||||
const oldState = {...metamaskState, ...appState} |
||||
|
||||
const state = reduceApp(oldState, { |
||||
type: actions.SHAPESHIFT_SUBVIEW, |
||||
value: { |
||||
marketinfo, |
||||
coinOptions, |
||||
}, |
||||
}) |
||||
|
||||
assert.equal(state.buyView.subview, 'ShapeShift') |
||||
assert.equal(state.buyView.formView.shapeshift, true) |
||||
assert.deepEqual(state.buyView.formView.marketinfo, marketinfo) |
||||
assert.deepEqual(state.buyView.formView.coinOptions, coinOptions) |
||||
assert.equal(state.buyView.buyAddress, '0xAddress') |
||||
assert.equal(state.buyView.amount, '12.00') |
||||
}) |
||||
|
||||
it('updates pair', () => { |
||||
const coinOptions = { |
||||
BTC: { |
||||
symbol: 'BTC', |
||||
name: 'Bitcoin', |
||||
image: 'https://shapeshift.io/images/coins/bitcoin.png', |
||||
imageSmall: 'https://shapeshift.io/images/coins-sm/bitcoin.png', |
||||
status: 'available', |
||||
minerFee: 0.00025, |
||||
}, |
||||
} |
||||
|
||||
const appState = { |
||||
appState: { |
||||
buyView: { |
||||
buyAddress: '0xAddress', |
||||
amount: '12.00', |
||||
formView: { |
||||
coinOptions, |
||||
}, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
const marketinfo = { |
||||
pair: 'BTC_ETH', |
||||
rate: 28.91191106, |
||||
minerFee: 0.0022, |
||||
limit: 0.76617432, |
||||
minimum: 0.00015323, |
||||
maxLimit: 0.76617432, |
||||
} |
||||
|
||||
const oldState = {...metamaskState, ...appState} |
||||
|
||||
const state = reduceApp(oldState, { |
||||
type: actions.PAIR_UPDATE, |
||||
value: { |
||||
marketinfo, |
||||
}, |
||||
}) |
||||
|
||||
assert.equal(state.buyView.subview, 'ShapeShift') |
||||
assert.equal(state.buyView.formView.shapeshift, true) |
||||
assert.deepEqual(state.buyView.formView.marketinfo, marketinfo) |
||||
assert.deepEqual(state.buyView.formView.coinOptions, coinOptions) |
||||
assert.equal(state.buyView.buyAddress, '0xAddress') |
||||
assert.equal(state.buyView.amount, '12.00') |
||||
}) |
||||
|
||||
it('shows QR', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SHOW_QR, |
||||
value: { |
||||
message: 'message', |
||||
data: 'data', |
||||
}, |
||||
}) |
||||
|
||||
assert.equal(state.qrRequested, true) |
||||
assert.equal(state.transForward, true) |
||||
assert.equal(state.Qr.message, 'message') |
||||
assert.equal(state.Qr.data, 'data') |
||||
}) |
||||
|
||||
it('shows qr view', () => { |
||||
const appState = { |
||||
appState: { |
||||
currentView: { |
||||
context: 'accounts', |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
const oldState = {...metamaskState, ...appState} |
||||
const state = reduceApp(oldState, { |
||||
type: actions.SHOW_QR_VIEW, |
||||
value: { |
||||
message: 'message', |
||||
data: 'data', |
||||
}, |
||||
}) |
||||
|
||||
assert.equal(state.currentView.name, 'qr') |
||||
assert.equal(state.currentView.context, 'accounts') |
||||
assert.equal(state.transForward, true) |
||||
assert.equal(state.Qr.message, 'message') |
||||
assert.equal(state.Qr.data, 'data') |
||||
}) |
||||
|
||||
it('set mouse user state', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SET_MOUSE_USER_STATE, |
||||
value: true, |
||||
}) |
||||
|
||||
assert.equal(state.isMouseUser, true) |
||||
}) |
||||
|
||||
it('sets gas loading', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.GAS_LOADING_STARTED, |
||||
}) |
||||
|
||||
assert.equal(state.gasIsLoading, true) |
||||
}) |
||||
|
||||
it('unsets gas loading', () => { |
||||
const gasLoadingState = { gasIsLoading: true } |
||||
const oldState = {...metamaskState, ...gasLoadingState} |
||||
const state = reduceApp(oldState, { |
||||
type: actions.GAS_LOADING_FINISHED, |
||||
}) |
||||
|
||||
assert.equal(state.gasIsLoading, false) |
||||
}) |
||||
|
||||
it('sets network nonce', () => { |
||||
const state = reduceApp(metamaskState, { |
||||
type: actions.SET_NETWORK_NONCE, |
||||
value: '33', |
||||
}) |
||||
|
||||
assert.equal(state.networkNonce, '33') |
||||
}) |
||||
}) |
@ -0,0 +1,576 @@ |
||||
import assert from 'assert' |
||||
import reduceMetamask from '../../../../../ui/app/reducers/metamask' |
||||
import * as actions from '../../../../../ui/app/actions' |
||||
|
||||
describe('MetaMask Reducers', () => { |
||||
|
||||
it('init state', () => { |
||||
const initState = reduceMetamask({metamask:{}}, {}) |
||||
assert(initState) |
||||
}) |
||||
|
||||
it('sets revealing seed to true and adds seed words to new state', () => { |
||||
const seedWordsState = reduceMetamask({}, { |
||||
type: actions.SHOW_NEW_VAULT_SEED, |
||||
value: 'test seed words', |
||||
}) |
||||
|
||||
assert.equal(seedWordsState.seedWords, 'test seed words') |
||||
assert.equal(seedWordsState.isRevealingSeedWords, true) |
||||
}) |
||||
|
||||
it('shows account page', () => { |
||||
const seedWordsState = { |
||||
metamask: { |
||||
seedwords: 'test seed words', |
||||
isRevealing: true, |
||||
}, |
||||
} |
||||
|
||||
const state = reduceMetamask(seedWordsState, { |
||||
type: actions.SHOW_ACCOUNTS_PAGE, |
||||
}) |
||||
|
||||
assert.equal(state.seedWords, undefined) |
||||
assert.equal(state.isRevealingSeedWords, false) |
||||
}) |
||||
|
||||
it('shows notice', () => { |
||||
const notice = { |
||||
id: 0, |
||||
read: false, |
||||
date: 'Date', |
||||
title: 'Title', |
||||
body: 'Body', |
||||
} |
||||
|
||||
const state = reduceMetamask({}, { |
||||
type: actions.SHOW_NOTICE, |
||||
value: notice, |
||||
}) |
||||
|
||||
assert.equal(state.noActiveNotices, false) |
||||
assert.equal(state.nextUnreadNotice, notice) |
||||
}) |
||||
|
||||
it('clears notice', () => { |
||||
|
||||
const notice = { |
||||
id: 0, |
||||
read: false, |
||||
date: 'Date', |
||||
title: 'Title', |
||||
body: 'Body', |
||||
} |
||||
|
||||
const noticesState = { |
||||
metamask: { |
||||
noActiveNotices: false, |
||||
nextUnreadNotice: notice, |
||||
}, |
||||
} |
||||
|
||||
const state = reduceMetamask(noticesState, { |
||||
type: actions.CLEAR_NOTICES, |
||||
}) |
||||
|
||||
assert.equal(state.noActiveNotices, true) |
||||
assert.equal(state.nextUnreadNotice, null) |
||||
}) |
||||
|
||||
it('unlocks MetaMask', () => { |
||||
const state = reduceMetamask({}, { |
||||
type: actions.UNLOCK_METAMASK, |
||||
value: 'test address', |
||||
}) |
||||
|
||||
assert.equal(state.isUnlocked, true) |
||||
assert.equal(state.isInitialized, true) |
||||
assert.equal(state.selectedAddress, 'test address') |
||||
}) |
||||
|
||||
it('locks MetaMask', () => { |
||||
const unlockMetaMaskState = { |
||||
metamask: { |
||||
isUnlocked: true, |
||||
isInitialzed: false, |
||||
selectedAddress: 'test address', |
||||
}, |
||||
} |
||||
const lockMetaMask = reduceMetamask(unlockMetaMaskState, { |
||||
type: actions.LOCK_METAMASK, |
||||
}) |
||||
|
||||
assert.equal(lockMetaMask.isUnlocked, false) |
||||
}) |
||||
|
||||
it('sets frequent rpc list', () => { |
||||
const state = reduceMetamask({}, { |
||||
type: actions.SET_RPC_LIST, |
||||
value: 'https://custom.rpc', |
||||
}) |
||||
|
||||
assert.equal(state.frequentRpcList, 'https://custom.rpc') |
||||
}) |
||||
|
||||
it('sets rpc target', () => { |
||||
const state = reduceMetamask({}, { |
||||
type: actions.SET_RPC_TARGET, |
||||
value: 'https://custom.rpc', |
||||
}) |
||||
|
||||
assert.equal(state.provider.rpcTarget, 'https://custom.rpc') |
||||
}) |
||||
|
||||
it('sets provider type', () => { |
||||
const state = reduceMetamask({}, { |
||||
type: actions.SET_PROVIDER_TYPE, |
||||
value: 'provider type', |
||||
}) |
||||
|
||||
assert.equal(state.provider.type, 'provider type') |
||||
}) |
||||
|
||||
describe('CompletedTx', () => { |
||||
const oldState = { |
||||
metamask: { |
||||
unapprovedTxs: { |
||||
1: { |
||||
id: 1, |
||||
time: 1538495996507, |
||||
status: 'unapproved', |
||||
metamaskNetworkId: 4, |
||||
loadingDefaults: false, |
||||
txParams: { |
||||
from: '0xAddress', |
||||
to: '0xAddress2', |
||||
value: '0x16345785d8a0000', |
||||
gas: '0x5208', |
||||
gasPrice: '0x3b9aca00', |
||||
}, |
||||
type: 'standard', |
||||
}, |
||||
2: { |
||||
test: 'Should persist', |
||||
}, |
||||
}, |
||||
unapprovedMsgs: { |
||||
1: { |
||||
id: 2, |
||||
msgParams: { |
||||
from: '0xAddress', |
||||
data: '0xData', |
||||
origin: 'test origin', |
||||
}, |
||||
time: 1538498521717, |
||||
status: 'unapproved', |
||||
type: 'eth_sign', |
||||
}, |
||||
2: { |
||||
test: 'Should Persist', |
||||
}, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
it('removes tx from new state if completed in action.', () => { |
||||
|
||||
const state = reduceMetamask(oldState, { |
||||
type: actions.COMPLETED_TX, |
||||
id: 1, |
||||
}) |
||||
|
||||
assert.equal(Object.keys(state.unapprovedTxs).length, 1) |
||||
assert.equal(state.unapprovedTxs[2].test, 'Should persist') |
||||
}) |
||||
|
||||
it('removes msg from new state if completed id in action', () => { |
||||
const state = reduceMetamask(oldState, { |
||||
type: actions.COMPLETED_TX, |
||||
id: 1, |
||||
}) |
||||
|
||||
assert.equal(Object.keys(state.unapprovedMsgs).length, 1) |
||||
assert.equal(state.unapprovedTxs[2].test, 'Should persist') |
||||
}) |
||||
}) |
||||
|
||||
it('shows new vault seed words and sets isRevealingSeedWords to true', () => { |
||||
const showNewVaultSeedState = reduceMetamask({}, { |
||||
type: actions.SHOW_NEW_VAULT_SEED, |
||||
value: 'test seed words', |
||||
}) |
||||
|
||||
assert.equal(showNewVaultSeedState.isRevealingSeedWords, true) |
||||
assert.equal(showNewVaultSeedState.seedWords, 'test seed words') |
||||
}) |
||||
|
||||
it('shows account detail', () => { |
||||
|
||||
const state = reduceMetamask({}, { |
||||
type: actions.SHOW_ACCOUNT_DETAIL, |
||||
value: 'test address', |
||||
}) |
||||
|
||||
assert.equal(state.isUnlocked, true) |
||||
assert.equal(state.isInitialized, true) |
||||
assert.equal(state.selectedAddress, 'test address') |
||||
}) |
||||
|
||||
it('sets select ', () => { |
||||
const state = reduceMetamask({}, { |
||||
type: actions.SET_SELECTED_TOKEN, |
||||
value: 'test token', |
||||
}) |
||||
|
||||
assert.equal(state.selectedTokenAddress, 'test token') |
||||
}) |
||||
|
||||
it('sets account label', () => { |
||||
const state = reduceMetamask({}, { |
||||
type: actions.SET_ACCOUNT_LABEL, |
||||
value: { |
||||
account: 'test account', |
||||
label: 'test label', |
||||
}, |
||||
}) |
||||
|
||||
assert.deepEqual(state.identities, { 'test account': { name: 'test label' } }) |
||||
}) |
||||
|
||||
it('sets current fiat', () => { |
||||
const value = { |
||||
currentCurrency: 'yen', |
||||
conversionRate: 3.14, |
||||
conversionDate: new Date(2018, 9), |
||||
} |
||||
|
||||
const state = reduceMetamask({}, { |
||||
type: actions.SET_CURRENT_FIAT, |
||||
value, |
||||
}) |
||||
|
||||
assert.equal(state.currentCurrency, value.currentCurrency) |
||||
assert.equal(state.conversionRate, value.conversionRate) |
||||
assert.equal(state.conversionDate, value.conversionDate) |
||||
}) |
||||
|
||||
it('updates tokens', () => { |
||||
const newTokens = { |
||||
'address': '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', |
||||
'decimals': 18, |
||||
'symbol': 'META', |
||||
} |
||||
|
||||
const state = reduceMetamask({}, { |
||||
type: actions.UPDATE_TOKENS, |
||||
newTokens, |
||||
}) |
||||
|
||||
assert.deepEqual(state.tokens, newTokens) |
||||
}) |
||||
|
||||
it('updates send gas limit', () => { |
||||
|
||||
const state = reduceMetamask({}, { |
||||
type: actions.UPDATE_GAS_LIMIT, |
||||
value: '0xGasLimit', |
||||
}) |
||||
|
||||
assert.equal(state.send.gasLimit, '0xGasLimit') |
||||
}) |
||||
|
||||
it('updates send gas price', () => { |
||||
const state = reduceMetamask({}, { |
||||
type: actions.UPDATE_GAS_PRICE, |
||||
value: '0xGasPrice', |
||||
}) |
||||
|
||||
assert.equal(state.send.gasPrice, '0xGasPrice') |
||||
}) |
||||
|
||||
it('toggles account menu ', () => { |
||||
const state = reduceMetamask({}, { |
||||
type: actions.TOGGLE_ACCOUNT_MENU, |
||||
}) |
||||
|
||||
assert.equal(state.isAccountMenuOpen, true) |
||||
}) |
||||
|
||||
it('updates gas total', () => { |
||||
const state = reduceMetamask({}, { |
||||
type: actions.UPDATE_GAS_TOTAL, |
||||
value: '0xGasTotal', |
||||
}) |
||||
|
||||
assert.equal(state.send.gasTotal, '0xGasTotal') |
||||
}) |
||||
|
||||
it('updates send token balance', () => { |
||||
const state = reduceMetamask({}, { |
||||
type: actions.UPDATE_SEND_TOKEN_BALANCE, |
||||
value: '0xTokenBalance', |
||||
}) |
||||
|
||||
assert.equal(state.send.tokenBalance, '0xTokenBalance') |
||||
}) |
||||
|
||||
it('updates data', () => { |
||||
const state = reduceMetamask({}, { |
||||
type: actions.UPDATE_SEND_HEX_DATA, |
||||
value: '0xData', |
||||
}) |
||||
|
||||
assert.equal(state.send.data, '0xData') |
||||
}) |
||||
|
||||
it('updates send to', () => { |
||||
const state = reduceMetamask({}, { |
||||
type: actions.UPDATE_SEND_TO, |
||||
value: { |
||||
to: '0xAddress', |
||||
nickname: 'nickname', |
||||
}, |
||||
}) |
||||
|
||||
assert.equal(state.send.to, '0xAddress') |
||||
assert.equal(state.send.toNickname, 'nickname') |
||||
}) |
||||
|
||||
it('update send from', () => { |
||||
const state = reduceMetamask({}, { |
||||
type: actions.UPDATE_SEND_FROM, |
||||
value: '0xAddress', |
||||
}) |
||||
|
||||
assert.equal(state.send.from, '0xAddress') |
||||
}) |
||||
|
||||
it('update send amount', () => { |
||||
const state = reduceMetamask({}, { |
||||
type: actions.UPDATE_SEND_AMOUNT, |
||||
value: '0xAmount', |
||||
}) |
||||
|
||||
assert.equal(state.send.amount, '0xAmount') |
||||
}) |
||||
|
||||
it('update send memo', () => { |
||||
const state = reduceMetamask({}, { |
||||
type: actions.UPDATE_SEND_MEMO, |
||||
value: '0xMemo', |
||||
}) |
||||
|
||||
assert.equal(state.send.memo, '0xMemo') |
||||
}) |
||||
|
||||
it('updates max mode', () => { |
||||
const state = reduceMetamask({}, { |
||||
type: actions.UPDATE_MAX_MODE, |
||||
value: true, |
||||
}) |
||||
|
||||
assert.equal(state.send.maxModeOn, true) |
||||
}) |
||||
|
||||
it('update send', () => { |
||||
const value = { |
||||
gasLimit: '0xGasLimit', |
||||
gasPrice: '0xGasPrice', |
||||
gasTotal: '0xGasTotal', |
||||
tokenBalance: '0xBalance', |
||||
from: '0xAddress', |
||||
to: '0xAddress', |
||||
toNickname: '', |
||||
maxModeOn: false, |
||||
amount: '0xAmount', |
||||
memo: '0xMemo', |
||||
errors: {}, |
||||
editingTransactionId: 22, |
||||
forceGasMin: '0xGas', |
||||
} |
||||
|
||||
const sendState = reduceMetamask({}, { |
||||
type: actions.UPDATE_SEND, |
||||
value, |
||||
}) |
||||
|
||||
assert.deepEqual(sendState.send, value) |
||||
}) |
||||
|
||||
it('clears send', () => { |
||||
const initStateSend = { |
||||
send: |
||||
{ gasLimit: null, |
||||
gasPrice: null, |
||||
gasTotal: null, |
||||
tokenBalance: null, |
||||
from: '', |
||||
to: '', |
||||
amount: '0x0', |
||||
memo: '', |
||||
errors: {}, |
||||
maxModeOn: false, |
||||
editingTransactionId: null, |
||||
forceGasMin: null, |
||||
toNickname: '' }, |
||||
} |
||||
|
||||
const sendState = { |
||||
send: { |
||||
gasLimit: '0xGasLimit', |
||||
gasPrice: '0xGasPrice', |
||||
gasTotal: '0xGasTotal', |
||||
tokenBalance: '0xBalance', |
||||
from: '0xAddress', |
||||
to: '0xAddress', |
||||
toNickname: '', |
||||
maxModeOn: false, |
||||
amount: '0xAmount', |
||||
memo: '0xMemo', |
||||
errors: {}, |
||||
editingTransactionId: 22, |
||||
forceGasMin: '0xGas', |
||||
}, |
||||
} |
||||
|
||||
|
||||
const state = reduceMetamask(sendState, { |
||||
type: actions.CLEAR_SEND, |
||||
}) |
||||
|
||||
assert.deepEqual(state.send, initStateSend.send) |
||||
}) |
||||
|
||||
it('updates value of tx by id', () => { |
||||
const oldState = { |
||||
metamask: { |
||||
selectedAddressTxList: [ |
||||
{ |
||||
id: 1, |
||||
txParams: 'foo', |
||||
}, |
||||
], |
||||
}, |
||||
} |
||||
|
||||
const state = reduceMetamask(oldState, { |
||||
type: actions.UPDATE_TRANSACTION_PARAMS, |
||||
id: 1, |
||||
value: 'bar', |
||||
}) |
||||
|
||||
assert.equal(state.selectedAddressTxList[0].txParams, 'bar') |
||||
}) |
||||
|
||||
it('updates pair for shapeshift', () => { |
||||
const state = reduceMetamask({}, { |
||||
type: actions.PAIR_UPDATE, |
||||
value: { |
||||
marketinfo: { |
||||
pair: 'test pair', |
||||
foo: 'bar', |
||||
}, |
||||
}, |
||||
}) |
||||
assert.equal(state.tokenExchangeRates['test pair'].pair, 'test pair') |
||||
}) |
||||
|
||||
it('upates pair and coin options for shapeshift subview', () => { |
||||
const state = reduceMetamask({}, { |
||||
type: actions.SHAPESHIFT_SUBVIEW, |
||||
value: { |
||||
marketinfo: { |
||||
pair: 'test pair', |
||||
}, |
||||
coinOptions: { |
||||
foo: 'bar', |
||||
}, |
||||
}, |
||||
}) |
||||
|
||||
assert.equal(state.coinOptions.foo, 'bar') |
||||
assert.equal(state.tokenExchangeRates['test pair'].pair, 'test pair') |
||||
}) |
||||
|
||||
it('sets blockies', () => { |
||||
const state = reduceMetamask({}, { |
||||
type: actions.SET_USE_BLOCKIE, |
||||
value: true, |
||||
}) |
||||
|
||||
assert.equal(state.useBlockie, true) |
||||
}) |
||||
|
||||
it('updates feature flag', () => { |
||||
const state = reduceMetamask({}, { |
||||
type: actions.UPDATE_FEATURE_FLAGS, |
||||
value: { |
||||
betaUI: true, |
||||
skipAnnounceBetaUI: true, |
||||
}, |
||||
}) |
||||
|
||||
assert.equal(state.featureFlags.betaUI, true) |
||||
assert.equal(state.featureFlags.skipAnnounceBetaUI, true) |
||||
}) |
||||
|
||||
it('updates network endpoint type', () => { |
||||
const state = reduceMetamask({}, { |
||||
type: actions.UPDATE_NETWORK_ENDPOINT_TYPE, |
||||
value: 'endpoint', |
||||
}) |
||||
|
||||
assert.equal(state.networkEndpointType, 'endpoint') |
||||
}) |
||||
|
||||
it('close welcome screen', () => { |
||||
const state = reduceMetamask({}, { |
||||
type: actions.CLOSE_WELCOME_SCREEN, |
||||
}) |
||||
|
||||
assert.equal(state.welcomeScreenSeen, true) |
||||
}) |
||||
|
||||
it('sets current locale', () => { |
||||
const state = reduceMetamask({}, { |
||||
type: actions.SET_CURRENT_LOCALE, |
||||
value: 'ge', |
||||
}) |
||||
|
||||
assert.equal(state.currentLocale, 'ge') |
||||
}) |
||||
|
||||
it('sets pending tokens ', () => { |
||||
const payload = { |
||||
'address': '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', |
||||
'decimals': 18, |
||||
'symbol': 'META', |
||||
} |
||||
|
||||
const pendingTokensState = reduceMetamask({}, { |
||||
type: actions.SET_PENDING_TOKENS, |
||||
payload, |
||||
}) |
||||
|
||||
assert.deepEqual(pendingTokensState.pendingTokens, payload) |
||||
}) |
||||
|
||||
it('clears pending tokens', () => { |
||||
const payload = { |
||||
'address': '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', |
||||
'decimals': 18, |
||||
'symbol': 'META', |
||||
} |
||||
|
||||
const pendingTokensState = { |
||||
pendingTokens: payload, |
||||
} |
||||
|
||||
const state = reduceMetamask(pendingTokensState, { |
||||
type: actions.CLEAR_PENDING_TOKENS, |
||||
}) |
||||
|
||||
assert.deepEqual(state.pendingTokens, {}) |
||||
}) |
||||
}) |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue