Merge pull request #7066 from MetaMask/Version-v7.1.0
Version 7.1.0feature/default_network_editable
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 692 B After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 93 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 17 KiB |
@ -0,0 +1,284 @@ |
|||||||
|
const ObservableStore = require('obs-store') |
||||||
|
const log = require('loglevel') |
||||||
|
const BN = require('bn.js') |
||||||
|
const createId = require('../lib/random-id') |
||||||
|
const { bnToHex, fetchWithTimeout } = require('../lib/util') |
||||||
|
const { |
||||||
|
MAINNET_CODE, |
||||||
|
ROPSTEN_CODE, |
||||||
|
RINKEYBY_CODE, |
||||||
|
KOVAN_CODE, |
||||||
|
ROPSTEN, |
||||||
|
RINKEBY, |
||||||
|
KOVAN, |
||||||
|
MAINNET, |
||||||
|
} = require('./network/enums') |
||||||
|
const networkTypeToIdMap = { |
||||||
|
[ROPSTEN]: String(ROPSTEN_CODE), |
||||||
|
[RINKEBY]: String(RINKEYBY_CODE), |
||||||
|
[KOVAN]: String(KOVAN_CODE), |
||||||
|
[MAINNET]: String(MAINNET_CODE), |
||||||
|
} |
||||||
|
const fetch = fetchWithTimeout({ |
||||||
|
timeout: 30000, |
||||||
|
}) |
||||||
|
|
||||||
|
class IncomingTransactionsController { |
||||||
|
|
||||||
|
constructor (opts = {}) { |
||||||
|
const { |
||||||
|
blockTracker, |
||||||
|
networkController, |
||||||
|
preferencesController, |
||||||
|
} = opts |
||||||
|
this.blockTracker = blockTracker |
||||||
|
this.networkController = networkController |
||||||
|
this.preferencesController = preferencesController |
||||||
|
this.getCurrentNetwork = () => networkController.getProviderConfig().type |
||||||
|
|
||||||
|
this._onLatestBlock = async (newBlockNumberHex) => { |
||||||
|
const selectedAddress = this.preferencesController.getSelectedAddress() |
||||||
|
const newBlockNumberDec = parseInt(newBlockNumberHex, 16) |
||||||
|
await this._update({ |
||||||
|
address: selectedAddress, |
||||||
|
newBlockNumberDec, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const initState = Object.assign({ |
||||||
|
incomingTransactions: {}, |
||||||
|
incomingTxLastFetchedBlocksByNetwork: { |
||||||
|
[ROPSTEN]: null, |
||||||
|
[RINKEBY]: null, |
||||||
|
[KOVAN]: null, |
||||||
|
[MAINNET]: null, |
||||||
|
}, |
||||||
|
}, opts.initState) |
||||||
|
this.store = new ObservableStore(initState) |
||||||
|
|
||||||
|
this.preferencesController.store.subscribe(pairwise((prevState, currState) => { |
||||||
|
const { featureFlags: { showIncomingTransactions: prevShowIncomingTransactions } = {} } = prevState |
||||||
|
const { featureFlags: { showIncomingTransactions: currShowIncomingTransactions } = {} } = currState |
||||||
|
|
||||||
|
if (currShowIncomingTransactions === prevShowIncomingTransactions) { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if (prevShowIncomingTransactions && !currShowIncomingTransactions) { |
||||||
|
this.stop() |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
this.start() |
||||||
|
})) |
||||||
|
|
||||||
|
this.preferencesController.store.subscribe(pairwise(async (prevState, currState) => { |
||||||
|
const { selectedAddress: prevSelectedAddress } = prevState |
||||||
|
const { selectedAddress: currSelectedAddress } = currState |
||||||
|
|
||||||
|
if (currSelectedAddress === prevSelectedAddress) { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
await this._update({ |
||||||
|
address: currSelectedAddress, |
||||||
|
}) |
||||||
|
})) |
||||||
|
|
||||||
|
this.networkController.on('networkDidChange', async (newType) => { |
||||||
|
const address = this.preferencesController.getSelectedAddress() |
||||||
|
await this._update({ |
||||||
|
address, |
||||||
|
networkType: newType, |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
start () { |
||||||
|
const { featureFlags = {} } = this.preferencesController.store.getState() |
||||||
|
const { showIncomingTransactions } = featureFlags |
||||||
|
|
||||||
|
if (!showIncomingTransactions) { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
this.blockTracker.removeListener('latest', this._onLatestBlock) |
||||||
|
this.blockTracker.addListener('latest', this._onLatestBlock) |
||||||
|
} |
||||||
|
|
||||||
|
stop () { |
||||||
|
this.blockTracker.removeListener('latest', this._onLatestBlock) |
||||||
|
} |
||||||
|
|
||||||
|
async _update ({ address, newBlockNumberDec, networkType } = {}) { |
||||||
|
try { |
||||||
|
const dataForUpdate = await this._getDataForUpdate({ address, newBlockNumberDec, networkType }) |
||||||
|
await this._updateStateWithNewTxData(dataForUpdate) |
||||||
|
} catch (err) { |
||||||
|
log.error(err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async _getDataForUpdate ({ address, newBlockNumberDec, networkType } = {}) { |
||||||
|
const { |
||||||
|
incomingTransactions: currentIncomingTxs, |
||||||
|
incomingTxLastFetchedBlocksByNetwork: currentBlocksByNetwork, |
||||||
|
} = this.store.getState() |
||||||
|
|
||||||
|
const network = networkType || this.getCurrentNetwork() |
||||||
|
const lastFetchBlockByCurrentNetwork = currentBlocksByNetwork[network] |
||||||
|
let blockToFetchFrom = lastFetchBlockByCurrentNetwork || newBlockNumberDec |
||||||
|
if (blockToFetchFrom === undefined) { |
||||||
|
blockToFetchFrom = parseInt(this.blockTracker.getCurrentBlock(), 16) |
||||||
|
} |
||||||
|
|
||||||
|
const { latestIncomingTxBlockNumber, txs: newTxs } = await this._fetchAll(address, blockToFetchFrom, network) |
||||||
|
|
||||||
|
return { |
||||||
|
latestIncomingTxBlockNumber, |
||||||
|
newTxs, |
||||||
|
currentIncomingTxs, |
||||||
|
currentBlocksByNetwork, |
||||||
|
fetchedBlockNumber: blockToFetchFrom, |
||||||
|
network, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async _updateStateWithNewTxData ({ |
||||||
|
latestIncomingTxBlockNumber, |
||||||
|
newTxs, |
||||||
|
currentIncomingTxs, |
||||||
|
currentBlocksByNetwork, |
||||||
|
fetchedBlockNumber, |
||||||
|
network, |
||||||
|
}) { |
||||||
|
const newLatestBlockHashByNetwork = latestIncomingTxBlockNumber |
||||||
|
? parseInt(latestIncomingTxBlockNumber, 10) + 1 |
||||||
|
: fetchedBlockNumber + 1 |
||||||
|
const newIncomingTransactions = { |
||||||
|
...currentIncomingTxs, |
||||||
|
} |
||||||
|
newTxs.forEach(tx => { newIncomingTransactions[tx.hash] = tx }) |
||||||
|
|
||||||
|
this.store.updateState({ |
||||||
|
incomingTxLastFetchedBlocksByNetwork: { |
||||||
|
...currentBlocksByNetwork, |
||||||
|
[network]: newLatestBlockHashByNetwork, |
||||||
|
}, |
||||||
|
incomingTransactions: newIncomingTransactions, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
async _fetchAll (address, fromBlock, networkType) { |
||||||
|
try { |
||||||
|
const fetchedTxResponse = await this._fetchTxs(address, fromBlock, networkType) |
||||||
|
return this._processTxFetchResponse(fetchedTxResponse) |
||||||
|
} catch (err) { |
||||||
|
log.error(err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async _fetchTxs (address, fromBlock, networkType) { |
||||||
|
let etherscanSubdomain = 'api' |
||||||
|
const currentNetworkID = networkTypeToIdMap[networkType] |
||||||
|
const supportedNetworkTypes = [ROPSTEN, RINKEBY, KOVAN, MAINNET] |
||||||
|
|
||||||
|
if (supportedNetworkTypes.indexOf(networkType) === -1) { |
||||||
|
return {} |
||||||
|
} |
||||||
|
|
||||||
|
if (networkType !== MAINNET) { |
||||||
|
etherscanSubdomain = `api-${networkType}` |
||||||
|
} |
||||||
|
const apiUrl = `https://${etherscanSubdomain}.etherscan.io` |
||||||
|
let url = `${apiUrl}/api?module=account&action=txlist&address=${address}&tag=latest&page=1` |
||||||
|
|
||||||
|
if (fromBlock) { |
||||||
|
url += `&startBlock=${parseInt(fromBlock, 10)}` |
||||||
|
} |
||||||
|
const response = await fetch(url) |
||||||
|
const parsedResponse = await response.json() |
||||||
|
|
||||||
|
return { |
||||||
|
...parsedResponse, |
||||||
|
address, |
||||||
|
currentNetworkID, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
_processTxFetchResponse ({ status, result, address, currentNetworkID }) { |
||||||
|
if (status !== '0' && result.length > 0) { |
||||||
|
const remoteTxList = {} |
||||||
|
const remoteTxs = [] |
||||||
|
result.forEach((tx) => { |
||||||
|
if (!remoteTxList[tx.hash]) { |
||||||
|
remoteTxs.push(this._normalizeTxFromEtherscan(tx, currentNetworkID)) |
||||||
|
remoteTxList[tx.hash] = 1 |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
const incomingTxs = remoteTxs.filter(tx => tx.txParams.to && tx.txParams.to.toLowerCase() === address.toLowerCase()) |
||||||
|
incomingTxs.sort((a, b) => (a.time < b.time ? -1 : 1)) |
||||||
|
|
||||||
|
let latestIncomingTxBlockNumber = null |
||||||
|
incomingTxs.forEach((tx) => { |
||||||
|
if ( |
||||||
|
tx.blockNumber && |
||||||
|
(!latestIncomingTxBlockNumber || |
||||||
|
parseInt(latestIncomingTxBlockNumber, 10) < parseInt(tx.blockNumber, 10)) |
||||||
|
) { |
||||||
|
latestIncomingTxBlockNumber = tx.blockNumber |
||||||
|
} |
||||||
|
}) |
||||||
|
return { |
||||||
|
latestIncomingTxBlockNumber, |
||||||
|
txs: incomingTxs, |
||||||
|
} |
||||||
|
} |
||||||
|
return { |
||||||
|
latestIncomingTxBlockNumber: null, |
||||||
|
txs: [], |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
_normalizeTxFromEtherscan (txMeta, currentNetworkID) { |
||||||
|
const time = parseInt(txMeta.timeStamp, 10) * 1000 |
||||||
|
const status = txMeta.isError === '0' ? 'confirmed' : 'failed' |
||||||
|
return { |
||||||
|
blockNumber: txMeta.blockNumber, |
||||||
|
id: createId(), |
||||||
|
metamaskNetworkId: currentNetworkID, |
||||||
|
status, |
||||||
|
time, |
||||||
|
txParams: { |
||||||
|
from: txMeta.from, |
||||||
|
gas: bnToHex(new BN(txMeta.gas)), |
||||||
|
gasPrice: bnToHex(new BN(txMeta.gasPrice)), |
||||||
|
nonce: bnToHex(new BN(txMeta.nonce)), |
||||||
|
to: txMeta.to, |
||||||
|
value: bnToHex(new BN(txMeta.value)), |
||||||
|
}, |
||||||
|
hash: txMeta.hash, |
||||||
|
transactionCategory: 'incoming', |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = IncomingTransactionsController |
||||||
|
|
||||||
|
function pairwise (fn) { |
||||||
|
let first = true |
||||||
|
let cache |
||||||
|
return (value) => { |
||||||
|
try { |
||||||
|
if (first) { |
||||||
|
first = false |
||||||
|
return fn(value, value) |
||||||
|
} else { |
||||||
|
return fn(cache, value) |
||||||
|
} |
||||||
|
} finally { |
||||||
|
cache = value |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
const version = 36 |
||||||
|
const clone = require('clone') |
||||||
|
|
||||||
|
/** |
||||||
|
* The purpose of this migration is to remove the {@code privacyMode} feature flag. |
||||||
|
*/ |
||||||
|
module.exports = { |
||||||
|
version, |
||||||
|
migrate: async function (originalVersionedData) { |
||||||
|
const versionedData = clone(originalVersionedData) |
||||||
|
versionedData.meta.version = version |
||||||
|
const state = versionedData.data |
||||||
|
versionedData.data = transformState(state) |
||||||
|
return versionedData |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
function transformState (state) { |
||||||
|
const { PreferencesController } = state |
||||||
|
|
||||||
|
if (PreferencesController) { |
||||||
|
const featureFlags = PreferencesController.featureFlags || {} |
||||||
|
|
||||||
|
if (typeof featureFlags.privacyMode !== 'undefined') { |
||||||
|
delete featureFlags.privacyMode |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return state |
||||||
|
} |
@ -1,89 +0,0 @@ |
|||||||
{ |
|
||||||
"metamask": { |
|
||||||
"currentCurrency": "USD", |
|
||||||
"lostAccounts": [ |
|
||||||
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc", |
|
||||||
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b" |
|
||||||
], |
|
||||||
"conversionRate": 11.06608791, |
|
||||||
"conversionDate": 1470421024, |
|
||||||
"isInitialized": true, |
|
||||||
"isUnlocked": true, |
|
||||||
"currentDomain": "example.com", |
|
||||||
"rpcTarget": "https://rawtestrpc.metamask.io/", |
|
||||||
"identities": { |
|
||||||
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": { |
|
||||||
"name": "Wallet 1", |
|
||||||
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc", |
|
||||||
"mayBeFauceting": false |
|
||||||
}, |
|
||||||
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": { |
|
||||||
"name": "Wallet 2", |
|
||||||
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b", |
|
||||||
"mayBeFauceting": false |
|
||||||
}, |
|
||||||
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": { |
|
||||||
"name": "Wallet 3", |
|
||||||
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823", |
|
||||||
"mayBeFauceting": false |
|
||||||
}, |
|
||||||
"0x704107d04affddd9b66ab9de3dd7b095852e9b69": { |
|
||||||
"name": "Wallet 4", |
|
||||||
"address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69", |
|
||||||
"mayBeFauceting": false |
|
||||||
} |
|
||||||
}, |
|
||||||
"unconfTxs": {}, |
|
||||||
"accounts": { |
|
||||||
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": { |
|
||||||
"code": "0x", |
|
||||||
"balance": "0x100000000000", |
|
||||||
"nonce": "0x0", |
|
||||||
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc" |
|
||||||
}, |
|
||||||
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": { |
|
||||||
"code": "0x", |
|
||||||
"nonce": "0x0", |
|
||||||
"balance": "0x100000000000", |
|
||||||
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b" |
|
||||||
}, |
|
||||||
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": { |
|
||||||
"code": "0x", |
|
||||||
"nonce": "0x0", |
|
||||||
"balance": "0x100000000000", |
|
||||||
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823" |
|
||||||
}, |
|
||||||
"0x704107d04affddd9b66ab9de3dd7b095852e9b69": { |
|
||||||
"code": "0x", |
|
||||||
"balance": "0x0", |
|
||||||
"nonce": "0x0", |
|
||||||
"address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69" |
|
||||||
} |
|
||||||
}, |
|
||||||
"transactions": [], |
|
||||||
"network": "2", |
|
||||||
"seedWords": null, |
|
||||||
"unconfMsgs": {}, |
|
||||||
"messages": [], |
|
||||||
"provider": { |
|
||||||
"type": "testnet" |
|
||||||
}, |
|
||||||
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc" |
|
||||||
}, |
|
||||||
"appState": { |
|
||||||
"menuOpen": false, |
|
||||||
"currentView": { |
|
||||||
"name": "accountDetail", |
|
||||||
"detailView": null, |
|
||||||
"context": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc" |
|
||||||
}, |
|
||||||
"accountDetail": { |
|
||||||
"subview": "transactions" |
|
||||||
}, |
|
||||||
"currentDomain": "127.0.0.1:9966", |
|
||||||
"transForward": true, |
|
||||||
"isLoading": false, |
|
||||||
"warning": null |
|
||||||
}, |
|
||||||
"identities": {} |
|
||||||
} |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 138 KiB |
@ -1,241 +0,0 @@ |
|||||||
const fs = require('fs') |
|
||||||
const path = require('path') |
|
||||||
const async = require('async') |
|
||||||
const promisify = require('pify') |
|
||||||
|
|
||||||
// start(/\.selectors.js/, generateSelectorTest).catch(console.error)
|
|
||||||
// start(/\.utils.js/, generateUtilTest).catch(console.error)
|
|
||||||
startContainer(/\.container.js/, generateContainerTest).catch(console.error) |
|
||||||
|
|
||||||
async function getAllFileNames (dirName) { |
|
||||||
const allNames = (await promisify(fs.readdir)(dirName)) |
|
||||||
const fileNames = allNames.filter(name => name.match(/^.+\./)) |
|
||||||
const dirNames = allNames.filter(name => name.match(/^[^.]+$/)) |
|
||||||
|
|
||||||
const fullPathDirNames = dirNames.map(d => `${dirName}/${d}`) |
|
||||||
const subNameArrays = await promisify(async.map)(fullPathDirNames, getAllFileNames) |
|
||||||
let subNames = [] |
|
||||||
subNameArrays.forEach(subNameArray => { subNames = [...subNames, ...subNameArray] }) |
|
||||||
|
|
||||||
return [ |
|
||||||
...fileNames.map(name => dirName + '/' + name), |
|
||||||
...subNames, |
|
||||||
] |
|
||||||
} |
|
||||||
|
|
||||||
/* |
|
||||||
async function start (fileRegEx, testGenerator) { |
|
||||||
const fileNames = await getAllFileNames('./ui/app') |
|
||||||
const sFiles = fileNames.filter(name => name.match(fileRegEx)) |
|
||||||
|
|
||||||
let sFileMethodNames |
|
||||||
let testFilePath |
|
||||||
async.each(sFiles, async (sFile, cb) => { |
|
||||||
const [, sRootPath, sPath] = sFile.match(/^(.+\/)([^/]+)$/) |
|
||||||
sFileMethodNames = Object.keys(require(__dirname + '/' + sFile)) |
|
||||||
|
|
||||||
testFilePath = sPath.replace('.', '-').replace('.', '.test.') |
|
||||||
|
|
||||||
await promisify(fs.writeFile)( |
|
||||||
`${__dirname}/${sRootPath}tests/${testFilePath}`, |
|
||||||
testGenerator(sPath, sFileMethodNames), |
|
||||||
'utf8' |
|
||||||
) |
|
||||||
}, (err) => { |
|
||||||
console.log(err) |
|
||||||
}) |
|
||||||
|
|
||||||
} |
|
||||||
*/ |
|
||||||
|
|
||||||
async function startContainer (fileRegEx) { |
|
||||||
const fileNames = await getAllFileNames('./ui/app') |
|
||||||
const sFiles = fileNames.filter(name => name.match(fileRegEx)) |
|
||||||
|
|
||||||
async.each(sFiles, async (sFile) => { |
|
||||||
console.log(`sFile`, sFile) |
|
||||||
const [, sRootPath, sPath] = sFile.match(/^(.+\/)([^/]+)$/) |
|
||||||
|
|
||||||
const testFilePath = sPath.replace('.', '-').replace('.', '.test.') |
|
||||||
|
|
||||||
await promisify(fs.readFile)( |
|
||||||
path.join(__dirname, sFile), |
|
||||||
'utf8', |
|
||||||
async (err, result) => { |
|
||||||
if (err) { |
|
||||||
console.log('Error: ', err) |
|
||||||
} else { |
|
||||||
console.log(`result`, result.length) |
|
||||||
const returnObjectStrings = result |
|
||||||
.match(/return\s(\{[\s\S]+?})\n}/g) |
|
||||||
.map(str => { |
|
||||||
return str |
|
||||||
.slice(0, str.length - 1) |
|
||||||
.slice(7) |
|
||||||
.replace(/\n/g, '') |
|
||||||
.replace(/\s\s+/g, ' ') |
|
||||||
|
|
||||||
}) |
|
||||||
const mapStateToPropsAssertionObject = returnObjectStrings[0] |
|
||||||
.replace(/\w+:\s\w+\([\w,\s]+\),/g, str => { |
|
||||||
const strKey = str.match(/^\w+/)[0] |
|
||||||
return strKey + ': \'mock' + str.match(/^\w+/)[0].replace(/^./, c => c.toUpperCase()) + ':mockState\',\n' |
|
||||||
}) |
|
||||||
.replace(/{\s\w.+/, firstLinePair => `{\n ${firstLinePair.slice(2)}`) |
|
||||||
.replace(/\w+:.+,/g, s => ` ${s}`) |
|
||||||
.replace(/}/g, s => ` ${s}`) |
|
||||||
let mapDispatchToPropsMethodNames |
|
||||||
if (returnObjectStrings[1]) { |
|
||||||
mapDispatchToPropsMethodNames = returnObjectStrings[1].match(/\s\w+:\s/g).map(str => str.match(/\w+/)[0]) |
|
||||||
} |
|
||||||
const proxyquireObject = ('{\n ' + result |
|
||||||
.match(/import\s{[\s\S]+?}\sfrom\s.+/g) |
|
||||||
.map(s => s.replace(/\n/g, '')) |
|
||||||
.map((s) => { |
|
||||||
const proxyKeys = s.match(/{.+}/)[0].match(/\w+/g) |
|
||||||
return '\'' + s.match(/'(.+)'/)[1] + '\': { ' + (proxyKeys.length > 1 |
|
||||||
? '\n ' + proxyKeys.join(': () => {},\n ') + ': () => {},\n ' |
|
||||||
: proxyKeys[0] + ': () => {},') + ' }' |
|
||||||
}) |
|
||||||
.join(',\n ') + '\n}') |
|
||||||
.replace('{ connect: () => {}, },', `{
|
|
||||||
connect: (ms, md) => { |
|
||||||
mapStateToProps = ms |
|
||||||
mapDispatchToProps = md |
|
||||||
return () => ({}) |
|
||||||
}, |
|
||||||
},`)
|
|
||||||
// console.log(`proxyquireObject`, proxyquireObject);
|
|
||||||
// console.log(`mapStateToPropsAssertionObject`, mapStateToPropsAssertionObject);
|
|
||||||
// console.log(`mapDispatchToPropsMethodNames`, mapDispatchToPropsMethodNames);
|
|
||||||
|
|
||||||
const containerTest = generateContainerTest(sPath, { |
|
||||||
mapStateToPropsAssertionObject, |
|
||||||
mapDispatchToPropsMethodNames, |
|
||||||
proxyquireObject, |
|
||||||
}) |
|
||||||
// console.log(`containerTest`, `${__dirname}/${sRootPath}tests/${testFilePath}`, containerTest);
|
|
||||||
console.log('----') |
|
||||||
console.log(`sRootPath`, sRootPath) |
|
||||||
console.log(`testFilePath`, testFilePath) |
|
||||||
await promisify(fs.writeFile)( |
|
||||||
`${__dirname}/${sRootPath}tests/${testFilePath}`, |
|
||||||
containerTest, |
|
||||||
'utf8' |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
) |
|
||||||
}, (err) => { |
|
||||||
console.log('123', err) |
|
||||||
}) |
|
||||||
|
|
||||||
} |
|
||||||
/* |
|
||||||
function generateMethodList (methodArray) { |
|
||||||
return methodArray.map(n => ' ' + n).join(',\n') + ',' |
|
||||||
} |
|
||||||
|
|
||||||
function generateMethodDescribeBlock (methodName, index) { |
|
||||||
const describeBlock = |
|
||||||
`${index ? ' ' : ''}describe('${methodName}()', () => {
|
|
||||||
it('should', () => { |
|
||||||
const state = {} |
|
||||||
|
|
||||||
assert.equal(${methodName}(state), ) |
|
||||||
}) |
|
||||||
})` |
|
||||||
return describeBlock |
|
||||||
} |
|
||||||
*/ |
|
||||||
function generateDispatchMethodDescribeBlock (methodName, index) { |
|
||||||
const describeBlock = |
|
||||||
`${index ? ' ' : ''}describe('${methodName}()', () => {
|
|
||||||
it('should dispatch an action', () => { |
|
||||||
mapDispatchToPropsObject.${methodName}() |
|
||||||
assert(dispatchSpy.calledOnce) |
|
||||||
}) |
|
||||||
})` |
|
||||||
return describeBlock |
|
||||||
} |
|
||||||
/* |
|
||||||
function generateMethodDescribeBlocks (methodArray) { |
|
||||||
return methodArray |
|
||||||
.map((methodName, index) => generateMethodDescribeBlock(methodName, index)) |
|
||||||
.join('\n\n') |
|
||||||
} |
|
||||||
*/ |
|
||||||
|
|
||||||
function generateDispatchMethodDescribeBlocks (methodArray) { |
|
||||||
return methodArray |
|
||||||
.map((methodName, index) => generateDispatchMethodDescribeBlock(methodName, index)) |
|
||||||
.join('\n\n') |
|
||||||
} |
|
||||||
|
|
||||||
/* |
|
||||||
function generateSelectorTest (name, methodArray) { |
|
||||||
return `import assert from 'assert'
|
|
||||||
import { |
|
||||||
${generateMethodList(methodArray)} |
|
||||||
} from '../${name}' |
|
||||||
|
|
||||||
describe('${name.match(/^[^.]+/)} selectors', () => { |
|
||||||
|
|
||||||
${generateMethodDescribeBlocks(methodArray)} |
|
||||||
|
|
||||||
})` |
|
||||||
} |
|
||||||
|
|
||||||
function generateUtilTest (name, methodArray) { |
|
||||||
return `import assert from 'assert'
|
|
||||||
import { |
|
||||||
${generateMethodList(methodArray)} |
|
||||||
} from '../${name}' |
|
||||||
|
|
||||||
describe('${name.match(/^[^.]+/)} utils', () => { |
|
||||||
|
|
||||||
${generateMethodDescribeBlocks(methodArray)} |
|
||||||
|
|
||||||
})` |
|
||||||
} |
|
||||||
*/ |
|
||||||
|
|
||||||
function generateContainerTest (sPath, { |
|
||||||
mapStateToPropsAssertionObject, |
|
||||||
mapDispatchToPropsMethodNames, |
|
||||||
proxyquireObject, |
|
||||||
}) { |
|
||||||
return `import assert from 'assert'
|
|
||||||
import proxyquire from 'proxyquire' |
|
||||||
import sinon from 'sinon' |
|
||||||
|
|
||||||
let mapStateToProps |
|
||||||
let mapDispatchToProps |
|
||||||
|
|
||||||
proxyquire('../${sPath}', ${proxyquireObject}) |
|
||||||
|
|
||||||
describe('${sPath.match(/^[^.]+/)} container', () => { |
|
||||||
|
|
||||||
describe('mapStateToProps()', () => { |
|
||||||
|
|
||||||
it('should map the correct properties to props', () => { |
|
||||||
assert.deepEqual(mapStateToProps('mockState'), ${mapStateToPropsAssertionObject}) |
|
||||||
}) |
|
||||||
|
|
||||||
}) |
|
||||||
|
|
||||||
describe('mapDispatchToProps()', () => { |
|
||||||
let dispatchSpy |
|
||||||
let mapDispatchToPropsObject |
|
||||||
|
|
||||||
beforeEach(() => { |
|
||||||
dispatchSpy = sinon.spy() |
|
||||||
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy) |
|
||||||
}) |
|
||||||
|
|
||||||
${mapDispatchToPropsMethodNames ? generateDispatchMethodDescribeBlocks(mapDispatchToPropsMethodNames) : 'delete'} |
|
||||||
|
|
||||||
}) |
|
||||||
|
|
||||||
})` |
|
||||||
} |
|
@ -0,0 +1,658 @@ |
|||||||
|
const assert = require('assert') |
||||||
|
const sinon = require('sinon') |
||||||
|
const proxyquire = require('proxyquire') |
||||||
|
const IncomingTransactionsController = proxyquire('../../../../app/scripts/controllers/incoming-transactions', { |
||||||
|
'../lib/random-id': () => 54321, |
||||||
|
}) |
||||||
|
|
||||||
|
const { |
||||||
|
ROPSTEN, |
||||||
|
RINKEBY, |
||||||
|
KOVAN, |
||||||
|
MAINNET, |
||||||
|
} = require('../../../../app/scripts/controllers/network/enums') |
||||||
|
|
||||||
|
describe('IncomingTransactionsController', () => { |
||||||
|
const EMPTY_INIT_STATE = { |
||||||
|
incomingTransactions: {}, |
||||||
|
incomingTxLastFetchedBlocksByNetwork: { |
||||||
|
[ROPSTEN]: null, |
||||||
|
[RINKEBY]: null, |
||||||
|
[KOVAN]: null, |
||||||
|
[MAINNET]: null, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
const NON_EMPTY_INIT_STATE = { |
||||||
|
incomingTransactions: { |
||||||
|
'0x123456': { id: 777 }, |
||||||
|
}, |
||||||
|
incomingTxLastFetchedBlocksByNetwork: { |
||||||
|
[ROPSTEN]: 1, |
||||||
|
[RINKEBY]: 2, |
||||||
|
[KOVAN]: 3, |
||||||
|
[MAINNET]: 4, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
const NON_EMPTY_INIT_STATE_WITH_FAKE_NETWORK_STATE = { |
||||||
|
incomingTransactions: { |
||||||
|
'0x123456': { id: 777 }, |
||||||
|
}, |
||||||
|
incomingTxLastFetchedBlocksByNetwork: { |
||||||
|
[ROPSTEN]: 1, |
||||||
|
[RINKEBY]: 2, |
||||||
|
[KOVAN]: 3, |
||||||
|
[MAINNET]: 4, |
||||||
|
FAKE_NETWORK: 1111, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
const MOCK_BLOCKTRACKER = { |
||||||
|
addListener: sinon.spy(), |
||||||
|
removeListener: sinon.spy(), |
||||||
|
testProperty: 'fakeBlockTracker', |
||||||
|
getCurrentBlock: () => '0xa', |
||||||
|
} |
||||||
|
|
||||||
|
const MOCK_NETWORK_CONTROLLER = { |
||||||
|
getProviderConfig: () => ({ type: 'FAKE_NETWORK' }), |
||||||
|
on: sinon.spy(), |
||||||
|
} |
||||||
|
|
||||||
|
const MOCK_PREFERENCES_CONTROLLER = { |
||||||
|
getSelectedAddress: sinon.stub().returns('0x0101'), |
||||||
|
store: { |
||||||
|
getState: sinon.stub().returns({ |
||||||
|
featureFlags: { |
||||||
|
showIncomingTransactions: true, |
||||||
|
}, |
||||||
|
}), |
||||||
|
subscribe: sinon.spy(), |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
describe('constructor', () => { |
||||||
|
it('should set up correct store, listeners and properties in the constructor', () => { |
||||||
|
const incomingTransactionsController = new IncomingTransactionsController({ |
||||||
|
blockTracker: MOCK_BLOCKTRACKER, |
||||||
|
networkController: MOCK_NETWORK_CONTROLLER, |
||||||
|
preferencesController: MOCK_PREFERENCES_CONTROLLER, |
||||||
|
initState: {}, |
||||||
|
}) |
||||||
|
sinon.spy(incomingTransactionsController, '_update') |
||||||
|
|
||||||
|
assert.deepEqual(incomingTransactionsController.blockTracker, MOCK_BLOCKTRACKER) |
||||||
|
assert.deepEqual(incomingTransactionsController.networkController, MOCK_NETWORK_CONTROLLER) |
||||||
|
assert.equal(incomingTransactionsController.preferencesController, MOCK_PREFERENCES_CONTROLLER) |
||||||
|
assert.equal(incomingTransactionsController.getCurrentNetwork(), 'FAKE_NETWORK') |
||||||
|
|
||||||
|
assert.deepEqual(incomingTransactionsController.store.getState(), EMPTY_INIT_STATE) |
||||||
|
|
||||||
|
assert(incomingTransactionsController.networkController.on.calledOnce) |
||||||
|
assert.equal(incomingTransactionsController.networkController.on.getCall(0).args[0], 'networkDidChange') |
||||||
|
const networkControllerListenerCallback = incomingTransactionsController.networkController.on.getCall(0).args[1] |
||||||
|
assert.equal(incomingTransactionsController._update.callCount, 0) |
||||||
|
networkControllerListenerCallback('testNetworkType') |
||||||
|
assert.equal(incomingTransactionsController._update.callCount, 1) |
||||||
|
assert.deepEqual(incomingTransactionsController._update.getCall(0).args[0], { |
||||||
|
address: '0x0101', |
||||||
|
networkType: 'testNetworkType', |
||||||
|
}) |
||||||
|
|
||||||
|
incomingTransactionsController._update.resetHistory() |
||||||
|
}) |
||||||
|
|
||||||
|
it('should set the store to a provided initial state', () => { |
||||||
|
const incomingTransactionsController = new IncomingTransactionsController({ |
||||||
|
blockTracker: MOCK_BLOCKTRACKER, |
||||||
|
networkController: MOCK_NETWORK_CONTROLLER, |
||||||
|
preferencesController: MOCK_PREFERENCES_CONTROLLER, |
||||||
|
initState: NON_EMPTY_INIT_STATE, |
||||||
|
}) |
||||||
|
|
||||||
|
assert.deepEqual(incomingTransactionsController.store.getState(), NON_EMPTY_INIT_STATE) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('#start', () => { |
||||||
|
it('should set up a listener for the latest block', () => { |
||||||
|
const incomingTransactionsController = new IncomingTransactionsController({ |
||||||
|
blockTracker: MOCK_BLOCKTRACKER, |
||||||
|
networkController: MOCK_NETWORK_CONTROLLER, |
||||||
|
preferencesController: MOCK_PREFERENCES_CONTROLLER, |
||||||
|
initState: {}, |
||||||
|
}) |
||||||
|
sinon.spy(incomingTransactionsController, '_update') |
||||||
|
|
||||||
|
incomingTransactionsController.start() |
||||||
|
|
||||||
|
assert(incomingTransactionsController.blockTracker.addListener.calledOnce) |
||||||
|
assert.equal(incomingTransactionsController.blockTracker.addListener.getCall(0).args[0], 'latest') |
||||||
|
const blockTrackerListenerCallback = incomingTransactionsController.blockTracker.addListener.getCall(0).args[1] |
||||||
|
assert.equal(incomingTransactionsController._update.callCount, 0) |
||||||
|
blockTrackerListenerCallback('0xabc') |
||||||
|
assert.equal(incomingTransactionsController._update.callCount, 1) |
||||||
|
assert.deepEqual(incomingTransactionsController._update.getCall(0).args[0], { |
||||||
|
address: '0x0101', |
||||||
|
newBlockNumberDec: 2748, |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('_getDataForUpdate', () => { |
||||||
|
it('should call fetchAll with the correct params when passed a new block number and the current network has no stored block', async () => { |
||||||
|
const incomingTransactionsController = new IncomingTransactionsController({ |
||||||
|
blockTracker: MOCK_BLOCKTRACKER, |
||||||
|
networkController: MOCK_NETWORK_CONTROLLER, |
||||||
|
preferencesController: MOCK_PREFERENCES_CONTROLLER, |
||||||
|
initState: NON_EMPTY_INIT_STATE, |
||||||
|
}) |
||||||
|
incomingTransactionsController._fetchAll = sinon.stub().returns({}) |
||||||
|
|
||||||
|
await incomingTransactionsController._getDataForUpdate({ address: 'fakeAddress', newBlockNumberDec: 999 }) |
||||||
|
|
||||||
|
assert(incomingTransactionsController._fetchAll.calledOnce) |
||||||
|
|
||||||
|
assert.deepEqual(incomingTransactionsController._fetchAll.getCall(0).args, [ |
||||||
|
'fakeAddress', 999, 'FAKE_NETWORK', |
||||||
|
]) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should call fetchAll with the correct params when passed a new block number but the current network has a stored block', async () => { |
||||||
|
const incomingTransactionsController = new IncomingTransactionsController({ |
||||||
|
blockTracker: MOCK_BLOCKTRACKER, |
||||||
|
networkController: MOCK_NETWORK_CONTROLLER, |
||||||
|
preferencesController: MOCK_PREFERENCES_CONTROLLER, |
||||||
|
initState: NON_EMPTY_INIT_STATE_WITH_FAKE_NETWORK_STATE, |
||||||
|
}) |
||||||
|
incomingTransactionsController._fetchAll = sinon.stub().returns({}) |
||||||
|
|
||||||
|
await incomingTransactionsController._getDataForUpdate({ address: 'fakeAddress', newBlockNumberDec: 999 }) |
||||||
|
|
||||||
|
assert(incomingTransactionsController._fetchAll.calledOnce) |
||||||
|
|
||||||
|
assert.deepEqual(incomingTransactionsController._fetchAll.getCall(0).args, [ |
||||||
|
'fakeAddress', 1111, 'FAKE_NETWORK', |
||||||
|
]) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should call fetchAll with the correct params when passed a new network type but no block info exists', async () => { |
||||||
|
const incomingTransactionsController = new IncomingTransactionsController({ |
||||||
|
blockTracker: MOCK_BLOCKTRACKER, |
||||||
|
networkController: MOCK_NETWORK_CONTROLLER, |
||||||
|
preferencesController: MOCK_PREFERENCES_CONTROLLER, |
||||||
|
initState: NON_EMPTY_INIT_STATE_WITH_FAKE_NETWORK_STATE, |
||||||
|
}) |
||||||
|
incomingTransactionsController._fetchAll = sinon.stub().returns({}) |
||||||
|
|
||||||
|
await incomingTransactionsController._getDataForUpdate({ |
||||||
|
address: 'fakeAddress', |
||||||
|
networkType: 'NEW_FAKE_NETWORK', |
||||||
|
}) |
||||||
|
|
||||||
|
assert(incomingTransactionsController._fetchAll.calledOnce) |
||||||
|
|
||||||
|
assert.deepEqual(incomingTransactionsController._fetchAll.getCall(0).args, [ |
||||||
|
'fakeAddress', 10, 'NEW_FAKE_NETWORK', |
||||||
|
]) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should call fetchAll with the correct params when passed a new block number but the current network has a stored block', async () => { |
||||||
|
const incomingTransactionsController = new IncomingTransactionsController({ |
||||||
|
blockTracker: MOCK_BLOCKTRACKER, |
||||||
|
networkController: MOCK_NETWORK_CONTROLLER, |
||||||
|
preferencesController: MOCK_PREFERENCES_CONTROLLER, |
||||||
|
initState: NON_EMPTY_INIT_STATE_WITH_FAKE_NETWORK_STATE, |
||||||
|
}) |
||||||
|
incomingTransactionsController._fetchAll = sinon.stub().returns({}) |
||||||
|
|
||||||
|
await incomingTransactionsController._getDataForUpdate({ address: 'fakeAddress', newBlockNumberDec: 999 }) |
||||||
|
|
||||||
|
assert(incomingTransactionsController._fetchAll.calledOnce) |
||||||
|
|
||||||
|
assert.deepEqual(incomingTransactionsController._fetchAll.getCall(0).args, [ |
||||||
|
'fakeAddress', 1111, 'FAKE_NETWORK', |
||||||
|
]) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should return the expected data', async () => { |
||||||
|
const incomingTransactionsController = new IncomingTransactionsController({ |
||||||
|
blockTracker: MOCK_BLOCKTRACKER, |
||||||
|
networkController: MOCK_NETWORK_CONTROLLER, |
||||||
|
preferencesController: MOCK_PREFERENCES_CONTROLLER, |
||||||
|
initState: NON_EMPTY_INIT_STATE_WITH_FAKE_NETWORK_STATE, |
||||||
|
}) |
||||||
|
incomingTransactionsController._fetchAll = sinon.stub().returns({ |
||||||
|
latestIncomingTxBlockNumber: 444, |
||||||
|
txs: [{ id: 555 }], |
||||||
|
}) |
||||||
|
|
||||||
|
const result = await incomingTransactionsController._getDataForUpdate({ |
||||||
|
address: 'fakeAddress', |
||||||
|
networkType: 'FAKE_NETWORK', |
||||||
|
}) |
||||||
|
|
||||||
|
assert.deepEqual(result, { |
||||||
|
latestIncomingTxBlockNumber: 444, |
||||||
|
newTxs: [{ id: 555 }], |
||||||
|
currentIncomingTxs: { |
||||||
|
'0x123456': { id: 777 }, |
||||||
|
}, |
||||||
|
currentBlocksByNetwork: { |
||||||
|
[ROPSTEN]: 1, |
||||||
|
[RINKEBY]: 2, |
||||||
|
[KOVAN]: 3, |
||||||
|
[MAINNET]: 4, |
||||||
|
FAKE_NETWORK: 1111, |
||||||
|
}, |
||||||
|
fetchedBlockNumber: 1111, |
||||||
|
network: 'FAKE_NETWORK', |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('_updateStateWithNewTxData', () => { |
||||||
|
const MOCK_INPUT_WITHOUT_LASTEST = { |
||||||
|
newTxs: [{ id: 555, hash: '0xfff' }], |
||||||
|
currentIncomingTxs: { |
||||||
|
'0x123456': { id: 777, hash: '0x123456' }, |
||||||
|
}, |
||||||
|
currentBlocksByNetwork: { |
||||||
|
[ROPSTEN]: 1, |
||||||
|
[RINKEBY]: 2, |
||||||
|
[KOVAN]: 3, |
||||||
|
[MAINNET]: 4, |
||||||
|
FAKE_NETWORK: 1111, |
||||||
|
}, |
||||||
|
fetchedBlockNumber: 1111, |
||||||
|
network: 'FAKE_NETWORK', |
||||||
|
} |
||||||
|
|
||||||
|
const MOCK_INPUT_WITH_LASTEST = { |
||||||
|
...MOCK_INPUT_WITHOUT_LASTEST, |
||||||
|
latestIncomingTxBlockNumber: 444, |
||||||
|
} |
||||||
|
|
||||||
|
it('should update state with correct blockhash and transactions when passed a truthy latestIncomingTxBlockNumber', async () => { |
||||||
|
const incomingTransactionsController = new IncomingTransactionsController({ |
||||||
|
blockTracker: MOCK_BLOCKTRACKER, |
||||||
|
networkController: MOCK_NETWORK_CONTROLLER, |
||||||
|
preferencesController: MOCK_PREFERENCES_CONTROLLER, |
||||||
|
initState: NON_EMPTY_INIT_STATE, |
||||||
|
}) |
||||||
|
sinon.spy(incomingTransactionsController.store, 'updateState') |
||||||
|
|
||||||
|
await incomingTransactionsController._updateStateWithNewTxData(MOCK_INPUT_WITH_LASTEST) |
||||||
|
|
||||||
|
assert(incomingTransactionsController.store.updateState.calledOnce) |
||||||
|
|
||||||
|
assert.deepEqual(incomingTransactionsController.store.updateState.getCall(0).args[0], { |
||||||
|
incomingTxLastFetchedBlocksByNetwork: { |
||||||
|
...MOCK_INPUT_WITH_LASTEST.currentBlocksByNetwork, |
||||||
|
'FAKE_NETWORK': 445, |
||||||
|
}, |
||||||
|
incomingTransactions: { |
||||||
|
'0x123456': { id: 777, hash: '0x123456' }, |
||||||
|
'0xfff': { id: 555, hash: '0xfff' }, |
||||||
|
}, |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should update state with correct blockhash and transactions when passed a falsy latestIncomingTxBlockNumber', async () => { |
||||||
|
const incomingTransactionsController = new IncomingTransactionsController({ |
||||||
|
blockTracker: MOCK_BLOCKTRACKER, |
||||||
|
networkController: MOCK_NETWORK_CONTROLLER, |
||||||
|
preferencesController: MOCK_PREFERENCES_CONTROLLER, |
||||||
|
initState: NON_EMPTY_INIT_STATE, |
||||||
|
}) |
||||||
|
sinon.spy(incomingTransactionsController.store, 'updateState') |
||||||
|
|
||||||
|
await incomingTransactionsController._updateStateWithNewTxData(MOCK_INPUT_WITHOUT_LASTEST) |
||||||
|
|
||||||
|
assert(incomingTransactionsController.store.updateState.calledOnce) |
||||||
|
|
||||||
|
assert.deepEqual(incomingTransactionsController.store.updateState.getCall(0).args[0], { |
||||||
|
incomingTxLastFetchedBlocksByNetwork: { |
||||||
|
...MOCK_INPUT_WITH_LASTEST.currentBlocksByNetwork, |
||||||
|
'FAKE_NETWORK': 1112, |
||||||
|
}, |
||||||
|
incomingTransactions: { |
||||||
|
'0x123456': { id: 777, hash: '0x123456' }, |
||||||
|
'0xfff': { id: 555, hash: '0xfff' }, |
||||||
|
}, |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('_fetchTxs', () => { |
||||||
|
const mockFetch = sinon.stub().returns(Promise.resolve({ |
||||||
|
json: () => Promise.resolve({ someKey: 'someValue' }), |
||||||
|
})) |
||||||
|
let tempFetch |
||||||
|
beforeEach(() => { |
||||||
|
tempFetch = global.fetch |
||||||
|
global.fetch = mockFetch |
||||||
|
}) |
||||||
|
|
||||||
|
afterEach(() => { |
||||||
|
global.fetch = tempFetch |
||||||
|
mockFetch.resetHistory() |
||||||
|
}) |
||||||
|
|
||||||
|
it('should call fetch with the expected url when passed an address, block number and supported network', async () => { |
||||||
|
const incomingTransactionsController = new IncomingTransactionsController({ |
||||||
|
blockTracker: MOCK_BLOCKTRACKER, |
||||||
|
networkController: MOCK_NETWORK_CONTROLLER, |
||||||
|
preferencesController: MOCK_PREFERENCES_CONTROLLER, |
||||||
|
initState: NON_EMPTY_INIT_STATE, |
||||||
|
}) |
||||||
|
|
||||||
|
await incomingTransactionsController._fetchTxs('0xfakeaddress', '789', ROPSTEN) |
||||||
|
|
||||||
|
assert(mockFetch.calledOnce) |
||||||
|
assert.equal(mockFetch.getCall(0).args[0], `https://api-${ROPSTEN}.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1&startBlock=789`) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should call fetch with the expected url when passed an address, block number and MAINNET', async () => { |
||||||
|
const incomingTransactionsController = new IncomingTransactionsController({ |
||||||
|
blockTracker: MOCK_BLOCKTRACKER, |
||||||
|
networkController: MOCK_NETWORK_CONTROLLER, |
||||||
|
preferencesController: MOCK_PREFERENCES_CONTROLLER, |
||||||
|
initState: NON_EMPTY_INIT_STATE, |
||||||
|
}) |
||||||
|
|
||||||
|
await incomingTransactionsController._fetchTxs('0xfakeaddress', '789', MAINNET) |
||||||
|
|
||||||
|
assert(mockFetch.calledOnce) |
||||||
|
assert.equal(mockFetch.getCall(0).args[0], `https://api.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1&startBlock=789`) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should call fetch with the expected url when passed an address and supported network, but a falsy block number', async () => { |
||||||
|
const incomingTransactionsController = new IncomingTransactionsController({ |
||||||
|
blockTracker: MOCK_BLOCKTRACKER, |
||||||
|
networkController: MOCK_NETWORK_CONTROLLER, |
||||||
|
preferencesController: MOCK_PREFERENCES_CONTROLLER, |
||||||
|
initState: NON_EMPTY_INIT_STATE, |
||||||
|
}) |
||||||
|
|
||||||
|
await incomingTransactionsController._fetchTxs('0xfakeaddress', null, ROPSTEN) |
||||||
|
|
||||||
|
assert(mockFetch.calledOnce) |
||||||
|
assert.equal(mockFetch.getCall(0).args[0], `https://api-${ROPSTEN}.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1`) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should not fetch and return an empty object when passed an unsported network', async () => { |
||||||
|
const incomingTransactionsController = new IncomingTransactionsController({ |
||||||
|
blockTracker: MOCK_BLOCKTRACKER, |
||||||
|
networkController: MOCK_NETWORK_CONTROLLER, |
||||||
|
preferencesController: MOCK_PREFERENCES_CONTROLLER, |
||||||
|
initState: NON_EMPTY_INIT_STATE, |
||||||
|
}) |
||||||
|
|
||||||
|
const result = await incomingTransactionsController._fetchTxs('0xfakeaddress', null, 'UNSUPPORTED_NETWORK') |
||||||
|
|
||||||
|
assert(mockFetch.notCalled) |
||||||
|
assert.deepEqual(result, {}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should return the results from the fetch call, plus the address and currentNetworkID, when passed an address, block number and supported network', async () => { |
||||||
|
const incomingTransactionsController = new IncomingTransactionsController({ |
||||||
|
blockTracker: MOCK_BLOCKTRACKER, |
||||||
|
networkController: MOCK_NETWORK_CONTROLLER, |
||||||
|
preferencesController: MOCK_PREFERENCES_CONTROLLER, |
||||||
|
initState: NON_EMPTY_INIT_STATE, |
||||||
|
}) |
||||||
|
|
||||||
|
const result = await incomingTransactionsController._fetchTxs('0xfakeaddress', '789', ROPSTEN) |
||||||
|
|
||||||
|
assert(mockFetch.calledOnce) |
||||||
|
assert.deepEqual(result, { |
||||||
|
someKey: 'someValue', |
||||||
|
address: '0xfakeaddress', |
||||||
|
currentNetworkID: 3, |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('_processTxFetchResponse', () => { |
||||||
|
it('should return a null block number and empty tx array if status is 0', () => { |
||||||
|
const incomingTransactionsController = new IncomingTransactionsController({ |
||||||
|
blockTracker: MOCK_BLOCKTRACKER, |
||||||
|
networkController: MOCK_NETWORK_CONTROLLER, |
||||||
|
preferencesController: MOCK_PREFERENCES_CONTROLLER, |
||||||
|
initState: NON_EMPTY_INIT_STATE, |
||||||
|
}) |
||||||
|
|
||||||
|
const result = incomingTransactionsController._processTxFetchResponse({ |
||||||
|
status: '0', |
||||||
|
result: [{ id: 1 }], |
||||||
|
address: '0xfakeaddress', |
||||||
|
}) |
||||||
|
|
||||||
|
assert.deepEqual(result, { |
||||||
|
latestIncomingTxBlockNumber: null, |
||||||
|
txs: [], |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should return a null block number and empty tx array if the passed result array is empty', () => { |
||||||
|
const incomingTransactionsController = new IncomingTransactionsController({ |
||||||
|
blockTracker: MOCK_BLOCKTRACKER, |
||||||
|
networkController: MOCK_NETWORK_CONTROLLER, |
||||||
|
preferencesController: MOCK_PREFERENCES_CONTROLLER, |
||||||
|
initState: NON_EMPTY_INIT_STATE, |
||||||
|
}) |
||||||
|
|
||||||
|
const result = incomingTransactionsController._processTxFetchResponse({ |
||||||
|
status: '1', |
||||||
|
result: [], |
||||||
|
address: '0xfakeaddress', |
||||||
|
}) |
||||||
|
|
||||||
|
assert.deepEqual(result, { |
||||||
|
latestIncomingTxBlockNumber: null, |
||||||
|
txs: [], |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should return the expected block number and tx list when passed data from a successful fetch', () => { |
||||||
|
const incomingTransactionsController = new IncomingTransactionsController({ |
||||||
|
blockTracker: MOCK_BLOCKTRACKER, |
||||||
|
networkController: MOCK_NETWORK_CONTROLLER, |
||||||
|
preferencesController: MOCK_PREFERENCES_CONTROLLER, |
||||||
|
initState: NON_EMPTY_INIT_STATE, |
||||||
|
}) |
||||||
|
|
||||||
|
incomingTransactionsController._normalizeTxFromEtherscan = (tx, currentNetworkID) => ({ |
||||||
|
...tx, |
||||||
|
currentNetworkID, |
||||||
|
normalized: true, |
||||||
|
}) |
||||||
|
|
||||||
|
const result = incomingTransactionsController._processTxFetchResponse({ |
||||||
|
status: '1', |
||||||
|
address: '0xfakeaddress', |
||||||
|
currentNetworkID: 'FAKE_NETWORK', |
||||||
|
result: [ |
||||||
|
{ |
||||||
|
hash: '0xabc123', |
||||||
|
txParams: { |
||||||
|
to: '0xfakeaddress', |
||||||
|
}, |
||||||
|
blockNumber: 5000, |
||||||
|
time: 10, |
||||||
|
}, |
||||||
|
{ |
||||||
|
hash: '0xabc123', |
||||||
|
txParams: { |
||||||
|
to: '0xfakeaddress', |
||||||
|
}, |
||||||
|
blockNumber: 5000, |
||||||
|
time: 10, |
||||||
|
}, |
||||||
|
{ |
||||||
|
hash: '0xabc1234', |
||||||
|
txParams: { |
||||||
|
to: '0xfakeaddress', |
||||||
|
}, |
||||||
|
blockNumber: 5000, |
||||||
|
time: 9, |
||||||
|
}, |
||||||
|
{ |
||||||
|
hash: '0xabc12345', |
||||||
|
txParams: { |
||||||
|
to: '0xfakeaddress', |
||||||
|
}, |
||||||
|
blockNumber: 5001, |
||||||
|
time: 11, |
||||||
|
}, |
||||||
|
{ |
||||||
|
hash: '0xabc123456', |
||||||
|
txParams: { |
||||||
|
to: '0xfakeaddress', |
||||||
|
}, |
||||||
|
blockNumber: 5001, |
||||||
|
time: 12, |
||||||
|
}, |
||||||
|
{ |
||||||
|
hash: '0xabc1234567', |
||||||
|
txParams: { |
||||||
|
to: '0xanotherFakeaddress', |
||||||
|
}, |
||||||
|
blockNumber: 5002, |
||||||
|
time: 13, |
||||||
|
}, |
||||||
|
], |
||||||
|
}) |
||||||
|
|
||||||
|
assert.deepEqual(result, { |
||||||
|
latestIncomingTxBlockNumber: 5001, |
||||||
|
txs: [ |
||||||
|
{ |
||||||
|
hash: '0xabc1234', |
||||||
|
txParams: { |
||||||
|
to: '0xfakeaddress', |
||||||
|
}, |
||||||
|
blockNumber: 5000, |
||||||
|
time: 9, |
||||||
|
normalized: true, |
||||||
|
currentNetworkID: 'FAKE_NETWORK', |
||||||
|
}, |
||||||
|
{ |
||||||
|
hash: '0xabc123', |
||||||
|
txParams: { |
||||||
|
to: '0xfakeaddress', |
||||||
|
}, |
||||||
|
blockNumber: 5000, |
||||||
|
time: 10, |
||||||
|
normalized: true, |
||||||
|
currentNetworkID: 'FAKE_NETWORK', |
||||||
|
}, |
||||||
|
{ |
||||||
|
hash: '0xabc12345', |
||||||
|
txParams: { |
||||||
|
to: '0xfakeaddress', |
||||||
|
}, |
||||||
|
blockNumber: 5001, |
||||||
|
time: 11, |
||||||
|
normalized: true, |
||||||
|
currentNetworkID: 'FAKE_NETWORK', |
||||||
|
}, |
||||||
|
{ |
||||||
|
hash: '0xabc123456', |
||||||
|
txParams: { |
||||||
|
to: '0xfakeaddress', |
||||||
|
}, |
||||||
|
blockNumber: 5001, |
||||||
|
time: 12, |
||||||
|
normalized: true, |
||||||
|
currentNetworkID: 'FAKE_NETWORK', |
||||||
|
}, |
||||||
|
], |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('_normalizeTxFromEtherscan', () => { |
||||||
|
it('should return the expected data when the tx is in error', () => { |
||||||
|
const incomingTransactionsController = new IncomingTransactionsController({ |
||||||
|
blockTracker: MOCK_BLOCKTRACKER, |
||||||
|
networkController: MOCK_NETWORK_CONTROLLER, |
||||||
|
preferencesController: MOCK_PREFERENCES_CONTROLLER, |
||||||
|
initState: NON_EMPTY_INIT_STATE, |
||||||
|
}) |
||||||
|
|
||||||
|
const result = incomingTransactionsController._normalizeTxFromEtherscan({ |
||||||
|
timeStamp: '4444', |
||||||
|
isError: '1', |
||||||
|
blockNumber: 333, |
||||||
|
from: '0xa', |
||||||
|
gas: '11', |
||||||
|
gasPrice: '12', |
||||||
|
nonce: '13', |
||||||
|
to: '0xe', |
||||||
|
value: '15', |
||||||
|
hash: '0xg', |
||||||
|
}, 'FAKE_NETWORK') |
||||||
|
|
||||||
|
assert.deepEqual(result, { |
||||||
|
blockNumber: 333, |
||||||
|
id: 54321, |
||||||
|
metamaskNetworkId: 'FAKE_NETWORK', |
||||||
|
status: 'failed', |
||||||
|
time: 4444000, |
||||||
|
txParams: { |
||||||
|
from: '0xa', |
||||||
|
gas: '0xb', |
||||||
|
gasPrice: '0xc', |
||||||
|
nonce: '0xd', |
||||||
|
to: '0xe', |
||||||
|
value: '0xf', |
||||||
|
}, |
||||||
|
hash: '0xg', |
||||||
|
transactionCategory: 'incoming', |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should return the expected data when the tx is not in error', () => { |
||||||
|
const incomingTransactionsController = new IncomingTransactionsController({ |
||||||
|
blockTracker: MOCK_BLOCKTRACKER, |
||||||
|
networkController: MOCK_NETWORK_CONTROLLER, |
||||||
|
preferencesController: MOCK_PREFERENCES_CONTROLLER, |
||||||
|
initState: NON_EMPTY_INIT_STATE, |
||||||
|
}) |
||||||
|
|
||||||
|
const result = incomingTransactionsController._normalizeTxFromEtherscan({ |
||||||
|
timeStamp: '4444', |
||||||
|
isError: '0', |
||||||
|
blockNumber: 333, |
||||||
|
from: '0xa', |
||||||
|
gas: '11', |
||||||
|
gasPrice: '12', |
||||||
|
nonce: '13', |
||||||
|
to: '0xe', |
||||||
|
value: '15', |
||||||
|
hash: '0xg', |
||||||
|
}, 'FAKE_NETWORK') |
||||||
|
|
||||||
|
assert.deepEqual(result, { |
||||||
|
blockNumber: 333, |
||||||
|
id: 54321, |
||||||
|
metamaskNetworkId: 'FAKE_NETWORK', |
||||||
|
status: 'confirmed', |
||||||
|
time: 4444000, |
||||||
|
txParams: { |
||||||
|
from: '0xa', |
||||||
|
gas: '0xb', |
||||||
|
gasPrice: '0xc', |
||||||
|
nonce: '0xd', |
||||||
|
to: '0xe', |
||||||
|
value: '0xf', |
||||||
|
}, |
||||||
|
hash: '0xg', |
||||||
|
transactionCategory: 'incoming', |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
@ -0,0 +1,260 @@ |
|||||||
|
const assert = require('assert') |
||||||
|
const sinon = require('sinon') |
||||||
|
const ProviderApprovalController = require('../../../../app/scripts/controllers/provider-approval') |
||||||
|
|
||||||
|
const mockLockedKeyringController = { |
||||||
|
memStore: { |
||||||
|
getState: () => ({ |
||||||
|
isUnlocked: false, |
||||||
|
}), |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
const mockUnlockedKeyringController = { |
||||||
|
memStore: { |
||||||
|
getState: () => ({ |
||||||
|
isUnlocked: true, |
||||||
|
}), |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
describe('ProviderApprovalController', () => { |
||||||
|
describe('#_handleProviderRequest', () => { |
||||||
|
it('should add a pending provider request when unlocked', () => { |
||||||
|
const controller = new ProviderApprovalController({ |
||||||
|
keyringController: mockUnlockedKeyringController, |
||||||
|
}) |
||||||
|
|
||||||
|
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg') |
||||||
|
assert.deepEqual(controller._getMergedState(), { |
||||||
|
approvedOrigins: {}, |
||||||
|
providerRequests: [{ |
||||||
|
origin: 'example.com', |
||||||
|
siteTitle: 'Example', |
||||||
|
siteImage: 'https://example.com/logo.svg', |
||||||
|
}], |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should add a pending provider request when locked', () => { |
||||||
|
const controller = new ProviderApprovalController({ |
||||||
|
keyringController: mockLockedKeyringController, |
||||||
|
}) |
||||||
|
|
||||||
|
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg') |
||||||
|
assert.deepEqual(controller._getMergedState(), { |
||||||
|
approvedOrigins: {}, |
||||||
|
providerRequests: [{ |
||||||
|
origin: 'example.com', |
||||||
|
siteTitle: 'Example', |
||||||
|
siteImage: 'https://example.com/logo.svg', |
||||||
|
}], |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should add a 2nd pending provider request when unlocked', () => { |
||||||
|
const controller = new ProviderApprovalController({ |
||||||
|
keyringController: mockUnlockedKeyringController, |
||||||
|
}) |
||||||
|
|
||||||
|
controller._handleProviderRequest('example1.com', 'Example 1', 'https://example1.com/logo.svg') |
||||||
|
controller._handleProviderRequest('example2.com', 'Example 2', 'https://example2.com/logo.svg') |
||||||
|
assert.deepEqual(controller._getMergedState(), { |
||||||
|
approvedOrigins: {}, |
||||||
|
providerRequests: [{ |
||||||
|
origin: 'example1.com', |
||||||
|
siteTitle: 'Example 1', |
||||||
|
siteImage: 'https://example1.com/logo.svg', |
||||||
|
}, { |
||||||
|
origin: 'example2.com', |
||||||
|
siteTitle: 'Example 2', |
||||||
|
siteImage: 'https://example2.com/logo.svg', |
||||||
|
}], |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should add a 2nd pending provider request when locked', () => { |
||||||
|
const controller = new ProviderApprovalController({ |
||||||
|
keyringController: mockLockedKeyringController, |
||||||
|
}) |
||||||
|
|
||||||
|
controller._handleProviderRequest('example1.com', 'Example 1', 'https://example1.com/logo.svg') |
||||||
|
controller._handleProviderRequest('example2.com', 'Example 2', 'https://example2.com/logo.svg') |
||||||
|
assert.deepEqual(controller._getMergedState(), { |
||||||
|
approvedOrigins: {}, |
||||||
|
providerRequests: [{ |
||||||
|
origin: 'example1.com', |
||||||
|
siteTitle: 'Example 1', |
||||||
|
siteImage: 'https://example1.com/logo.svg', |
||||||
|
}, { |
||||||
|
origin: 'example2.com', |
||||||
|
siteTitle: 'Example 2', |
||||||
|
siteImage: 'https://example2.com/logo.svg', |
||||||
|
}], |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should call openPopup when unlocked and when given', () => { |
||||||
|
const openPopup = sinon.spy() |
||||||
|
const controller = new ProviderApprovalController({ |
||||||
|
openPopup, |
||||||
|
keyringController: mockUnlockedKeyringController, |
||||||
|
}) |
||||||
|
|
||||||
|
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg') |
||||||
|
assert.ok(openPopup.calledOnce) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should call openPopup when locked and when given', () => { |
||||||
|
const openPopup = sinon.spy() |
||||||
|
const controller = new ProviderApprovalController({ |
||||||
|
openPopup, |
||||||
|
keyringController: mockLockedKeyringController, |
||||||
|
}) |
||||||
|
|
||||||
|
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg') |
||||||
|
assert.ok(openPopup.calledOnce) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should NOT call openPopup when unlocked and when the domain has already been approved', () => { |
||||||
|
const openPopup = sinon.spy() |
||||||
|
const controller = new ProviderApprovalController({ |
||||||
|
openPopup, |
||||||
|
keyringController: mockUnlockedKeyringController, |
||||||
|
}) |
||||||
|
|
||||||
|
controller.store.updateState({ |
||||||
|
approvedOrigins: { |
||||||
|
'example.com': { |
||||||
|
siteTitle: 'Example', |
||||||
|
siteImage: 'https://example.com/logo.svg', |
||||||
|
}, |
||||||
|
}, |
||||||
|
}) |
||||||
|
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg') |
||||||
|
assert.ok(openPopup.notCalled) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('#approveProviderRequestByOrigin', () => { |
||||||
|
it('should mark the origin as approved and remove the provider request', () => { |
||||||
|
const controller = new ProviderApprovalController({ |
||||||
|
keyringController: mockUnlockedKeyringController, |
||||||
|
}) |
||||||
|
|
||||||
|
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg') |
||||||
|
controller.approveProviderRequestByOrigin('example.com') |
||||||
|
assert.deepEqual(controller._getMergedState(), { |
||||||
|
providerRequests: [], |
||||||
|
approvedOrigins: { |
||||||
|
'example.com': { |
||||||
|
siteTitle: 'Example', |
||||||
|
siteImage: 'https://example.com/logo.svg', |
||||||
|
}, |
||||||
|
}, |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should mark the origin as approved and multiple requests for the same domain', () => { |
||||||
|
const controller = new ProviderApprovalController({ |
||||||
|
keyringController: mockUnlockedKeyringController, |
||||||
|
}) |
||||||
|
|
||||||
|
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg') |
||||||
|
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg') |
||||||
|
controller.approveProviderRequestByOrigin('example.com') |
||||||
|
assert.deepEqual(controller._getMergedState(), { |
||||||
|
providerRequests: [], |
||||||
|
approvedOrigins: { |
||||||
|
'example.com': { |
||||||
|
siteTitle: 'Example', |
||||||
|
siteImage: 'https://example.com/logo.svg', |
||||||
|
}, |
||||||
|
}, |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should mark the origin as approved without a provider request', () => { |
||||||
|
const controller = new ProviderApprovalController({ |
||||||
|
keyringController: mockUnlockedKeyringController, |
||||||
|
}) |
||||||
|
|
||||||
|
controller.approveProviderRequestByOrigin('example.com') |
||||||
|
assert.deepEqual(controller._getMergedState(), { |
||||||
|
providerRequests: [], |
||||||
|
approvedOrigins: { |
||||||
|
'example.com': { |
||||||
|
siteTitle: null, |
||||||
|
siteImage: null, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('#rejectProviderRequestByOrigin', () => { |
||||||
|
it('should remove the origin from approved', () => { |
||||||
|
const controller = new ProviderApprovalController({ |
||||||
|
keyringController: mockUnlockedKeyringController, |
||||||
|
}) |
||||||
|
|
||||||
|
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg') |
||||||
|
controller.approveProviderRequestByOrigin('example.com') |
||||||
|
controller.rejectProviderRequestByOrigin('example.com') |
||||||
|
assert.deepEqual(controller._getMergedState(), { |
||||||
|
providerRequests: [], |
||||||
|
approvedOrigins: {}, |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should reject the origin even without a pending request', () => { |
||||||
|
const controller = new ProviderApprovalController({ |
||||||
|
keyringController: mockUnlockedKeyringController, |
||||||
|
}) |
||||||
|
|
||||||
|
controller.rejectProviderRequestByOrigin('example.com') |
||||||
|
assert.deepEqual(controller._getMergedState(), { |
||||||
|
providerRequests: [], |
||||||
|
approvedOrigins: {}, |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('#clearApprovedOrigins', () => { |
||||||
|
it('should clear the approved origins', () => { |
||||||
|
const controller = new ProviderApprovalController({ |
||||||
|
keyringController: mockUnlockedKeyringController, |
||||||
|
}) |
||||||
|
|
||||||
|
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg') |
||||||
|
controller.approveProviderRequestByOrigin('example.com') |
||||||
|
controller.clearApprovedOrigins() |
||||||
|
assert.deepEqual(controller._getMergedState(), { |
||||||
|
providerRequests: [], |
||||||
|
approvedOrigins: {}, |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('#shouldExposeAccounts', () => { |
||||||
|
it('should return true for an approved origin', () => { |
||||||
|
const controller = new ProviderApprovalController({ |
||||||
|
keyringController: mockUnlockedKeyringController, |
||||||
|
}) |
||||||
|
|
||||||
|
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg') |
||||||
|
controller.approveProviderRequestByOrigin('example.com') |
||||||
|
assert.ok(controller.shouldExposeAccounts('example.com')) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should return false for an origin not yet approved', () => { |
||||||
|
const controller = new ProviderApprovalController({ |
||||||
|
keyringController: mockUnlockedKeyringController, |
||||||
|
}) |
||||||
|
|
||||||
|
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg') |
||||||
|
controller.approveProviderRequestByOrigin('example.com') |
||||||
|
assert.ok(!controller.shouldExposeAccounts('bad.website')) |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
@ -0,0 +1,119 @@ |
|||||||
|
const assert = require('assert') |
||||||
|
const migration36 = require('../../../app/scripts/migrations/036') |
||||||
|
|
||||||
|
describe('migration #36', () => { |
||||||
|
it('should update the version metadata', (done) => { |
||||||
|
const oldStorage = { |
||||||
|
'meta': { |
||||||
|
'version': 35, |
||||||
|
}, |
||||||
|
'data': {}, |
||||||
|
} |
||||||
|
|
||||||
|
migration36.migrate(oldStorage) |
||||||
|
.then((newStorage) => { |
||||||
|
assert.deepEqual(newStorage.meta, { |
||||||
|
'version': 36, |
||||||
|
}) |
||||||
|
done() |
||||||
|
}) |
||||||
|
.catch(done) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should remove privacyMode if featureFlags.privacyMode was false', (done) => { |
||||||
|
const oldStorage = { |
||||||
|
'meta': {}, |
||||||
|
'data': { |
||||||
|
'PreferencesController': { |
||||||
|
'featureFlags': { |
||||||
|
'privacyMode': false, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
migration36.migrate(oldStorage) |
||||||
|
.then((newStorage) => { |
||||||
|
assert.deepEqual(newStorage.data.PreferencesController, { |
||||||
|
'featureFlags': { |
||||||
|
}, |
||||||
|
}) |
||||||
|
done() |
||||||
|
}) |
||||||
|
.catch(done) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should remove privacyMode if featureFlags.privacyMode was true', (done) => { |
||||||
|
const oldStorage = { |
||||||
|
'meta': {}, |
||||||
|
'data': { |
||||||
|
'PreferencesController': { |
||||||
|
'featureFlags': { |
||||||
|
'privacyMode': true, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
migration36.migrate(oldStorage) |
||||||
|
.then((newStorage) => { |
||||||
|
assert.deepEqual(newStorage.data.PreferencesController, { |
||||||
|
'featureFlags': { |
||||||
|
}, |
||||||
|
}) |
||||||
|
done() |
||||||
|
}) |
||||||
|
.catch(done) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should NOT change any state if privacyMode does not exist', (done) => { |
||||||
|
const oldStorage = { |
||||||
|
'meta': {}, |
||||||
|
'data': { |
||||||
|
'PreferencesController': { |
||||||
|
'migratedPrivacyMode': true, |
||||||
|
'featureFlags': { |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
migration36.migrate(oldStorage) |
||||||
|
.then((newStorage) => { |
||||||
|
assert.deepEqual(newStorage.data, oldStorage.data) |
||||||
|
done() |
||||||
|
}) |
||||||
|
.catch(done) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should NOT change any state if PreferencesController is missing', (done) => { |
||||||
|
const oldStorage = { |
||||||
|
'meta': {}, |
||||||
|
'data': {}, |
||||||
|
} |
||||||
|
|
||||||
|
migration36.migrate(oldStorage) |
||||||
|
.then((newStorage) => { |
||||||
|
assert.deepEqual(newStorage.data, oldStorage.data) |
||||||
|
done() |
||||||
|
}) |
||||||
|
.catch(done) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should NOT change any state if featureFlags is missing', (done) => { |
||||||
|
const oldStorage = { |
||||||
|
'meta': {}, |
||||||
|
'data': { |
||||||
|
'PreferencesController': { |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
migration36.migrate(oldStorage) |
||||||
|
.then((newStorage) => { |
||||||
|
assert.deepEqual(newStorage.data, oldStorage.data) |
||||||
|
done() |
||||||
|
}) |
||||||
|
.catch(done) |
||||||
|
}) |
||||||
|
}) |
@ -0,0 +1,31 @@ |
|||||||
|
import React, { PureComponent } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
|
||||||
|
export default class ConnectedSiteRow extends PureComponent { |
||||||
|
static defaultProps = { |
||||||
|
siteTitle: null, |
||||||
|
siteImage: null, |
||||||
|
onDelete: () => {}, |
||||||
|
} |
||||||
|
|
||||||
|
static propTypes = { |
||||||
|
siteTitle: PropTypes.string, |
||||||
|
siteImage: PropTypes.string, |
||||||
|
origin: PropTypes.string.isRequired, |
||||||
|
onDelete: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
const { |
||||||
|
origin, |
||||||
|
onDelete, |
||||||
|
} = this.props |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="connected-site-row"> |
||||||
|
<div className="connected-site-row__origin">{origin}</div> |
||||||
|
<div className="connected-site-row__delete" onClick={onDelete}><i className="fa fa-trash" /></div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './connected-site-row.component' |
@ -0,0 +1,14 @@ |
|||||||
|
.connected-site-row { |
||||||
|
display: flex; |
||||||
|
flex-direction: row; |
||||||
|
justify-content: space-between; |
||||||
|
align-items: center; |
||||||
|
|
||||||
|
&__origin { |
||||||
|
font-family: monospace; |
||||||
|
} |
||||||
|
|
||||||
|
&__delete { |
||||||
|
padding: 8px; |
||||||
|
} |
||||||
|
} |