commit
6bb92a8672
File diff suppressed because it is too large
Load Diff
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), |
||||
}) |
||||
|
||||
try { |
||||
// handle error-like non-error exceptions
|
||||
rewriteErrorLikeExceptions(report) |
||||
// simplify certain complex error messages (e.g. Ethjs)
|
||||
simplifyErrorMessages(report) |
||||
// modify report urls
|
||||
rewriteReportUrls(report) |
||||
} catch (err) { |
||||
console.warn(err) |
||||
} |
||||
// make request normally
|
||||
client._makeRequest(opts) |
||||
}, |
||||
Sentry.configureScope(scope => { |
||||
scope.setExtra('isBrave', isBrave) |
||||
}) |
||||
client.install() |
||||
|
||||
return Raven |
||||
} |
||||
function rewriteReport(report) { |
||||
try { |
||||
// 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) |
||||
} |
||||
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
File diff suppressed because one or more lines are too long
@ -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