parent
0739618a61
commit
6bc8cc819a
@ -1,3 +1,6 @@ |
|||||||
{ |
{ |
||||||
"exceptions": ["https://nodesecurity.io/advisories/566"] |
"exceptions": [ |
||||||
|
"https://nodesecurity.io/advisories/566", |
||||||
|
"https://nodesecurity.io/advisories/157" |
||||||
|
] |
||||||
} |
} |
||||||
|
@ -0,0 +1,10 @@ |
|||||||
|
# Storybook |
||||||
|
We're currently using [Storybook](https://storybook.js.org/) as part of our design system. To run Storybook and test some of our UI components, clone the repo and run the following: |
||||||
|
``` |
||||||
|
npm install |
||||||
|
npm run storybook |
||||||
|
``` |
||||||
|
You should then see: |
||||||
|
> info Storybook started on => http://localhost:6006/ |
||||||
|
|
||||||
|
In your browser, navigate to http://localhost:6006/ to see the Storybook application. From here, you'll be able to easily view components and even modify some of their properties. |
@ -0,0 +1,2 @@ |
|||||||
|
import '@storybook/addon-knobs/register' |
||||||
|
import '@storybook/addon-actions/register' |
@ -0,0 +1,11 @@ |
|||||||
|
import { configure } from '@storybook/react' |
||||||
|
import '../ui/app/css/index.scss' |
||||||
|
|
||||||
|
const req = require.context('../ui/app/components', true, /\.stories\.js$/) |
||||||
|
|
||||||
|
function loadStories () { |
||||||
|
require('./decorators') |
||||||
|
req.keys().forEach((filename) => req(filename)) |
||||||
|
} |
||||||
|
|
||||||
|
configure(loadStories, module) |
@ -0,0 +1,21 @@ |
|||||||
|
import React from 'react' |
||||||
|
import { addDecorator } from '@storybook/react' |
||||||
|
import { withInfo } from '@storybook/addon-info' |
||||||
|
import { withKnobs } from '@storybook/addon-knobs/react' |
||||||
|
|
||||||
|
const styles = { |
||||||
|
height: '100vh', |
||||||
|
display: 'flex', |
||||||
|
justifyContent: 'center', |
||||||
|
alignItems: 'center', |
||||||
|
} |
||||||
|
|
||||||
|
const CenterDecorator = story => ( |
||||||
|
<div style={styles}> |
||||||
|
{ story() } |
||||||
|
</div> |
||||||
|
) |
||||||
|
|
||||||
|
addDecorator((story, context) => withInfo()(story)(context)) |
||||||
|
addDecorator(withKnobs) |
||||||
|
addDecorator(CenterDecorator) |
@ -0,0 +1,37 @@ |
|||||||
|
const path = require('path') |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
module: { |
||||||
|
rules: [ |
||||||
|
{ |
||||||
|
test: /\.(woff(2)?|ttf|eot|svg|otf)(\?v=\d+\.\d+\.\d+)?$/, |
||||||
|
loaders: [{ |
||||||
|
loader: 'file-loader', |
||||||
|
options: { |
||||||
|
name: '[name].[ext]', |
||||||
|
outputPath: 'fonts/', |
||||||
|
}, |
||||||
|
}], |
||||||
|
}, |
||||||
|
{ |
||||||
|
test: /\.scss$/, |
||||||
|
loaders: [ |
||||||
|
'style-loader', |
||||||
|
'css-loader', |
||||||
|
'resolve-url-loader', |
||||||
|
{ |
||||||
|
loader: 'sass-loader', |
||||||
|
options: { |
||||||
|
sourceMap: true, |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
resolve: { |
||||||
|
alias: { |
||||||
|
'./fonts/Font_Awesome': path.resolve(__dirname, '../fonts/Font_Awesome'), |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.7 KiB |
@ -0,0 +1,92 @@ |
|||||||
|
# Transaction Controller |
||||||
|
|
||||||
|
Transaction Controller is an aggregate of sub-controllers and trackers |
||||||
|
exposed to the MetaMask controller. |
||||||
|
|
||||||
|
- txStateManager |
||||||
|
responsible for the state of a transaction and |
||||||
|
storing the transaction |
||||||
|
- pendingTxTracker |
||||||
|
watching blocks for transactions to be include |
||||||
|
and emitting confirmed events |
||||||
|
- txGasUtil |
||||||
|
gas calculations and safety buffering |
||||||
|
- nonceTracker |
||||||
|
calculating nonces |
||||||
|
|
||||||
|
## Flow diagram of processing a transaction |
||||||
|
|
||||||
|
![transaction-flow](../../../../docs/transaction-flow.png) |
||||||
|
|
||||||
|
## txMeta's & txParams |
||||||
|
|
||||||
|
A txMeta is the "meta" object it has all the random bits of info we need about a transaction on it. txParams are sacred every thing on txParams gets signed so it must |
||||||
|
be a valid key and be hex prefixed except for the network number. Extra stuff must go on the txMeta! |
||||||
|
|
||||||
|
Here is a txMeta too look at: |
||||||
|
|
||||||
|
```js |
||||||
|
txMeta = { |
||||||
|
"id": 2828415030114568, // unique id for this txMeta used for look ups |
||||||
|
"time": 1524094064821, // time of creation |
||||||
|
"status": "confirmed", |
||||||
|
"metamaskNetworkId": "1524091532133", //the network id for the transaction |
||||||
|
"loadingDefaults": false, // used to tell the ui when we are done calculatyig gass defaults |
||||||
|
"txParams": { // the txParams object |
||||||
|
"from": "0x8acce2391c0d510a6c5e5d8f819a678f79b7e675", |
||||||
|
"to": "0x8acce2391c0d510a6c5e5d8f819a678f79b7e675", |
||||||
|
"value": "0x0", |
||||||
|
"gasPrice": "0x3b9aca00", |
||||||
|
"gas": "0x7b0c", |
||||||
|
"nonce": "0x0" |
||||||
|
}, |
||||||
|
"history": [{ //debug |
||||||
|
"id": 2828415030114568, |
||||||
|
"time": 1524094064821, |
||||||
|
"status": "unapproved", |
||||||
|
"metamaskNetworkId": "1524091532133", |
||||||
|
"loadingDefaults": true, |
||||||
|
"txParams": { |
||||||
|
"from": "0x8acce2391c0d510a6c5e5d8f819a678f79b7e675", |
||||||
|
"to": "0x8acce2391c0d510a6c5e5d8f819a678f79b7e675", |
||||||
|
"value": "0x0" |
||||||
|
} |
||||||
|
}, |
||||||
|
[ |
||||||
|
{ |
||||||
|
"op": "add", |
||||||
|
"path": "/txParams/gasPrice", |
||||||
|
"value": "0x3b9aca00" |
||||||
|
}, |
||||||
|
...], // I've removed most of history for this |
||||||
|
"gasPriceSpecified": false, //whether or not the user/dapp has specified gasPrice |
||||||
|
"gasLimitSpecified": false, //whether or not the user/dapp has specified gas |
||||||
|
"estimatedGas": "5208", |
||||||
|
"origin": "MetaMask", //debug |
||||||
|
"nonceDetails": { |
||||||
|
"params": { |
||||||
|
"highestLocallyConfirmed": 0, |
||||||
|
"highestSuggested": 0, |
||||||
|
"nextNetworkNonce": 0 |
||||||
|
}, |
||||||
|
"local": { |
||||||
|
"name": "local", |
||||||
|
"nonce": 0, |
||||||
|
"details": { |
||||||
|
"startPoint": 0, |
||||||
|
"highest": 0 |
||||||
|
} |
||||||
|
}, |
||||||
|
"network": { |
||||||
|
"name": "network", |
||||||
|
"nonce": 0, |
||||||
|
"details": { |
||||||
|
"baseCount": 0 |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
"rawTx": "0xf86980843b9aca00827b0c948acce2391c0d510a6c5e5d8f819a678f79b7e67580808602c5b5de66eea05c01a320b96ac730cb210ca56d2cb71fa360e1fc2c21fa5cf333687d18eb323fa02ed05987a6e5fd0f2459fcff80710b76b83b296454ad9a37594a0ccb4643ea90", // used for rebroadcast |
||||||
|
"hash": "0xa45ba834b97c15e6ff4ed09badd04ecd5ce884b455eb60192cdc73bcc583972a", |
||||||
|
"submittedTime": 1524094077902 // time of the attempt to submit the raw tx to the network, used in the ui to show the retry button |
||||||
|
} |
||||||
|
``` |
@ -0,0 +1,99 @@ |
|||||||
|
const { |
||||||
|
addHexPrefix, |
||||||
|
isValidAddress, |
||||||
|
} = require('ethereumjs-util') |
||||||
|
|
||||||
|
/** |
||||||
|
@module |
||||||
|
*/ |
||||||
|
module.exports = { |
||||||
|
normalizeTxParams, |
||||||
|
validateTxParams, |
||||||
|
validateFrom, |
||||||
|
validateRecipient, |
||||||
|
getFinalStates, |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// functions that handle normalizing of that key in txParams
|
||||||
|
const normalizers = { |
||||||
|
from: from => addHexPrefix(from).toLowerCase(), |
||||||
|
to: to => addHexPrefix(to).toLowerCase(), |
||||||
|
nonce: nonce => addHexPrefix(nonce), |
||||||
|
value: value => addHexPrefix(value), |
||||||
|
data: data => addHexPrefix(data), |
||||||
|
gas: gas => addHexPrefix(gas), |
||||||
|
gasPrice: gasPrice => addHexPrefix(gasPrice), |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
normalizes txParams |
||||||
|
@param txParams {object} |
||||||
|
@returns {object} normalized txParams |
||||||
|
*/ |
||||||
|
function normalizeTxParams (txParams) { |
||||||
|
// apply only keys in the normalizers
|
||||||
|
const normalizedTxParams = {} |
||||||
|
for (const key in normalizers) { |
||||||
|
if (txParams[key]) normalizedTxParams[key] = normalizers[key](txParams[key]) |
||||||
|
} |
||||||
|
return normalizedTxParams |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
validates txParams |
||||||
|
@param txParams {object} |
||||||
|
*/ |
||||||
|
function validateTxParams (txParams) { |
||||||
|
validateFrom(txParams) |
||||||
|
validateRecipient(txParams) |
||||||
|
if ('value' in txParams) { |
||||||
|
const value = txParams.value.toString() |
||||||
|
if (value.includes('-')) { |
||||||
|
throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`) |
||||||
|
} |
||||||
|
|
||||||
|
if (value.includes('.')) { |
||||||
|
throw new Error(`Invalid transaction value of ${txParams.value} number must be in wei`) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
validates the from field in txParams |
||||||
|
@param txParams {object} |
||||||
|
*/ |
||||||
|
function validateFrom (txParams) { |
||||||
|
if (!(typeof txParams.from === 'string')) throw new Error(`Invalid from address ${txParams.from} not a string`) |
||||||
|
if (!isValidAddress(txParams.from)) throw new Error('Invalid from address') |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
validates the to field in txParams |
||||||
|
@param txParams {object} |
||||||
|
*/ |
||||||
|
function validateRecipient (txParams) { |
||||||
|
if (txParams.to === '0x' || txParams.to === null) { |
||||||
|
if (txParams.data) { |
||||||
|
delete txParams.to |
||||||
|
} else { |
||||||
|
throw new Error('Invalid recipient address') |
||||||
|
} |
||||||
|
} else if (txParams.to !== undefined && !isValidAddress(txParams.to)) { |
||||||
|
throw new Error('Invalid recipient address') |
||||||
|
} |
||||||
|
return txParams |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
@returns an {array} of states that can be considered final |
||||||
|
*/ |
||||||
|
function getFinalStates () { |
||||||
|
return [ |
||||||
|
'rejected', // the user has responded no!
|
||||||
|
'confirmed', // the tx has been included in a block.
|
||||||
|
'failed', // the tx failed for some reason, included on tx data.
|
||||||
|
'dropped', // the tx nonce was already used
|
||||||
|
] |
||||||
|
} |
||||||
|
|
@ -0,0 +1,49 @@ |
|||||||
|
const fs = require('fs') |
||||||
|
const { SourceMapConsumer } = require('source-map') |
||||||
|
|
||||||
|
//
|
||||||
|
// Utility to help check if sourcemaps are working
|
||||||
|
//
|
||||||
|
// searches `dist/chrome/inpage.js` for "new Error" statements
|
||||||
|
// and prints their source lines using the sourcemaps.
|
||||||
|
// if not working it may error or print minified garbage
|
||||||
|
//
|
||||||
|
|
||||||
|
start() |
||||||
|
|
||||||
|
async function start() { |
||||||
|
const rawBuild = fs.readFileSync(__dirname + '/../dist/chrome/inpage.js', 'utf8') |
||||||
|
const rawSourceMap = fs.readFileSync(__dirname + '/../dist/sourcemaps/inpage.js.map', 'utf8') |
||||||
|
const consumer = await new SourceMapConsumer(rawSourceMap) |
||||||
|
|
||||||
|
console.log('hasContentsOfAllSources:', consumer.hasContentsOfAllSources(), '\n') |
||||||
|
console.log('sources:') |
||||||
|
consumer.sources.map((sourcePath) => console.log(sourcePath)) |
||||||
|
|
||||||
|
console.log('\nexamining "new Error" statements:\n') |
||||||
|
const sourceLines = rawBuild.split('\n') |
||||||
|
sourceLines.map(line => indicesOf('new Error', line)) |
||||||
|
.forEach((errorIndices, lineIndex) => { |
||||||
|
// if (errorIndex === null) return console.log('line does not contain "new Error"')
|
||||||
|
errorIndices.forEach((errorIndex) => { |
||||||
|
const position = { line: lineIndex + 1, column: errorIndex } |
||||||
|
const result = consumer.originalPositionFor(position) |
||||||
|
if (!result.source) return console.warn(`!! missing source for position: ${position}`) |
||||||
|
// filter out deps distributed minified without sourcemaps
|
||||||
|
if (result.source === 'node_modules/browserify/node_modules/browser-pack/_prelude.js') return // minified mess
|
||||||
|
if (result.source === 'node_modules/web3/dist/web3.min.js') return // minified mess
|
||||||
|
const sourceContent = consumer.sourceContentFor(result.source) |
||||||
|
const sourceLines = sourceContent.split('\n') |
||||||
|
const line = sourceLines[result.line-1] |
||||||
|
console.log(`\n========================== ${result.source} ====================================\n`) |
||||||
|
console.log(line) |
||||||
|
console.log(`\n==============================================================================\n`) |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
function indicesOf(substring, string) { |
||||||
|
var a=[],i=-1; |
||||||
|
while((i=string.indexOf(substring,i+1)) >= 0) a.push(i); |
||||||
|
return a; |
||||||
|
} |
After Width: | Height: | Size: 138 KiB |
@ -0,0 +1,234 @@ |
|||||||
|
const fs = require('fs') |
||||||
|
const async = require('async') |
||||||
|
const path = require('path') |
||||||
|
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 rootPath = path.join(__dirname, 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) => { |
||||||
|
let [, 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, testGenerator) { |
||||||
|
const fileNames = await getAllFileNames('./ui/app') |
||||||
|
const sFiles = fileNames.filter(name => name.match(fileRegEx)) |
||||||
|
|
||||||
|
let sFileMethodNames |
||||||
|
async.each(sFiles, async (sFile, cb) => { |
||||||
|
console.log(`sFile`, sFile); |
||||||
|
let [, sRootPath, sPath] = sFile.match(/^(.+\/)([^/]+)$/) |
||||||
|
|
||||||
|
let testFilePath = sPath.replace('.', '-').replace('.', '.test.') |
||||||
|
|
||||||
|
await promisify(fs.readFile)( |
||||||
|
__dirname + '/' + sFile, |
||||||
|
'utf8', |
||||||
|
async (err, result) => { |
||||||
|
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, i) => { |
||||||
|
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'} |
||||||
|
|
||||||
|
}) |
||||||
|
|
||||||
|
})` |
||||||
|
} |
@ -1,6 +1,6 @@ |
|||||||
MetaMask is beta software. |
MetaMask is beta software. |
||||||
|
|
||||||
When you log in to MetaMask, your current account is visible to every new site you visit. |
When you log in to MetaMask, your current account's address is visible to every new site you visit. This can be used to look up your account balances of Ether and other tokens. |
||||||
|
|
||||||
For your privacy, for now, please sign out of MetaMask when you're done using a site. |
For your privacy, for now, please sign out of MetaMask when you're done using a site. |
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,314 @@ |
|||||||
|
const fs = require('fs') |
||||||
|
const mkdirp = require('mkdirp') |
||||||
|
const path = require('path') |
||||||
|
const assert = require('assert') |
||||||
|
const pify = require('pify') |
||||||
|
const webdriver = require('selenium-webdriver') |
||||||
|
const until = require('selenium-webdriver/lib/until') |
||||||
|
const By = webdriver.By |
||||||
|
const { delay, buildChromeWebDriver } = require('../func') |
||||||
|
|
||||||
|
describe('Metamask popup page', function () { |
||||||
|
let driver, accountAddress, tokenAddress, extensionId |
||||||
|
|
||||||
|
this.timeout(0) |
||||||
|
|
||||||
|
before(async function () { |
||||||
|
const extPath = path.resolve('dist/chrome') |
||||||
|
driver = buildChromeWebDriver(extPath) |
||||||
|
await driver.get('chrome://extensions') |
||||||
|
await delay(500) |
||||||
|
}) |
||||||
|
|
||||||
|
afterEach(async function () { |
||||||
|
if (this.currentTest.state === 'failed') { |
||||||
|
await verboseReportOnFailure(this.currentTest) |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
after(async function () { |
||||||
|
await driver.quit() |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Setup', function () { |
||||||
|
|
||||||
|
it('switches to Chrome extensions list', async function () { |
||||||
|
const tabs = await driver.getAllWindowHandles() |
||||||
|
await driver.switchTo().window(tabs[0]) |
||||||
|
await delay(300) |
||||||
|
}) |
||||||
|
|
||||||
|
it(`selects MetaMask's extension id and opens it in the current tab`, async function () { |
||||||
|
extensionId = await getExtensionId() |
||||||
|
await driver.get(`chrome-extension://${extensionId}/popup.html`) |
||||||
|
await delay(500) |
||||||
|
}) |
||||||
|
|
||||||
|
it('sets provider type to localhost', async function () { |
||||||
|
await driver.wait(until.elementLocated(By.css('#app-content')), 300) |
||||||
|
await setProviderType('localhost') |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Account Creation', () => { |
||||||
|
|
||||||
|
it('matches MetaMask title', async () => { |
||||||
|
const title = await driver.getTitle() |
||||||
|
assert.equal(title, 'MetaMask', 'title matches MetaMask') |
||||||
|
}) |
||||||
|
|
||||||
|
it('shows privacy notice', async () => { |
||||||
|
await driver.wait(async () => { |
||||||
|
const privacyHeader = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > h3')).getText() |
||||||
|
assert.equal(privacyHeader, 'PRIVACY NOTICE', 'shows privacy notice')
|
||||||
|
return privacyHeader === 'PRIVACY NOTICE' |
||||||
|
}, 300) |
||||||
|
await driver.findElement(By.css('button')).click() |
||||||
|
}) |
||||||
|
|
||||||
|
it('show terms of use', async () => { |
||||||
|
await driver.wait(async () => { |
||||||
|
const terms = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > h3')).getText() |
||||||
|
assert.equal(terms, 'TERMS OF USE', 'shows terms of use') |
||||||
|
return terms === 'TERMS OF USE' |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('checks if the TOU button is disabled', async () => { |
||||||
|
const button = await driver.findElement(By.css('button')).isEnabled() |
||||||
|
assert.equal(button, false, 'disabled continue button') |
||||||
|
const element = await driver.findElement(By.linkText('Attributions')) |
||||||
|
await driver.executeScript('arguments[0].scrollIntoView(true)', element) |
||||||
|
await delay(300) |
||||||
|
}) |
||||||
|
|
||||||
|
it('allows the button to be clicked when scrolled to the bottom of TOU', async () => { |
||||||
|
const button = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > button')) |
||||||
|
const buttonEnabled = await button.isEnabled() |
||||||
|
assert.equal(buttonEnabled, true, 'enabled continue button') |
||||||
|
await button.click() |
||||||
|
}) |
||||||
|
|
||||||
|
it('accepts password with length of eight', async () => { |
||||||
|
const passwordBox = await driver.findElement(By.id('password-box')) |
||||||
|
const passwordBoxConfirm = await driver.findElement(By.id('password-box-confirm')) |
||||||
|
const button = await driver.findElements(By.css('button')) |
||||||
|
|
||||||
|
await passwordBox.sendKeys('123456789') |
||||||
|
await passwordBoxConfirm.sendKeys('123456789') |
||||||
|
await button[0].click() |
||||||
|
await delay(500) |
||||||
|
}) |
||||||
|
|
||||||
|
it('shows value was created and seed phrase', async () => { |
||||||
|
await delay(300) |
||||||
|
const seedPhrase = await driver.findElement(By.css('.twelve-word-phrase')).getText() |
||||||
|
assert.equal(seedPhrase.split(' ').length, 12) |
||||||
|
const continueAfterSeedPhrase = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > button:nth-child(4)')) |
||||||
|
assert.equal(await continueAfterSeedPhrase.getText(), `I'VE COPIED IT SOMEWHERE SAFE`) |
||||||
|
await continueAfterSeedPhrase.click() |
||||||
|
await delay(300) |
||||||
|
}) |
||||||
|
|
||||||
|
it('shows account address', async function () { |
||||||
|
accountAddress = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div > div:nth-child(1) > flex-column > div.flex-row > div')).getText() |
||||||
|
}) |
||||||
|
|
||||||
|
it('logs out of the vault', async () => { |
||||||
|
await driver.findElement(By.css('.sandwich-expando')).click() |
||||||
|
await delay(500) |
||||||
|
const logoutButton = await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)')) |
||||||
|
assert.equal(await logoutButton.getText(), 'Log Out') |
||||||
|
await logoutButton.click() |
||||||
|
}) |
||||||
|
|
||||||
|
it('accepts account password after lock', async () => { |
||||||
|
await delay(500) |
||||||
|
await driver.findElement(By.id('password-box')).sendKeys('123456789') |
||||||
|
await driver.findElement(By.css('button')).click() |
||||||
|
await delay(500) |
||||||
|
}) |
||||||
|
|
||||||
|
it('shows QR code option', async () => { |
||||||
|
await delay(300) |
||||||
|
await driver.findElement(By.css('.fa-ellipsis-h')).click() |
||||||
|
await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div:nth-child(1) > flex-column > div.name-label > div > span > i > div > div > li:nth-child(3)')).click() |
||||||
|
await delay(300) |
||||||
|
}) |
||||||
|
|
||||||
|
it('checks QR code address is the same as account details address', async () => { |
||||||
|
const QRaccountAddress = await driver.findElement(By.css('.ellip-address')).getText() |
||||||
|
assert.equal(accountAddress.toLowerCase(), QRaccountAddress) |
||||||
|
await driver.findElement(By.css('.fa-arrow-left')).click() |
||||||
|
await delay(500) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Import Ganache seed phrase', function () { |
||||||
|
it('logs out', async function () { |
||||||
|
await driver.findElement(By.css('.sandwich-expando')).click() |
||||||
|
await delay(200) |
||||||
|
const logOut = await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)')) |
||||||
|
assert.equal(await logOut.getText(), 'Log Out') |
||||||
|
await logOut.click() |
||||||
|
await delay(300) |
||||||
|
}) |
||||||
|
|
||||||
|
it('restores from seed phrase', async function () { |
||||||
|
const restoreSeedLink = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div.flex-row.flex-center.flex-grow > p')) |
||||||
|
assert.equal(await restoreSeedLink.getText(), 'Restore from seed phrase') |
||||||
|
await restoreSeedLink.click() |
||||||
|
await delay(100) |
||||||
|
}) |
||||||
|
|
||||||
|
it('adds seed phrase', async function () { |
||||||
|
const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent' |
||||||
|
const seedTextArea = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > textarea')) |
||||||
|
await seedTextArea.sendKeys(testSeedPhrase) |
||||||
|
|
||||||
|
await driver.findElement(By.id('password-box')).sendKeys('123456789') |
||||||
|
await driver.findElement(By.id('password-box-confirm')).sendKeys('123456789') |
||||||
|
await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div > button:nth-child(2)')).click() |
||||||
|
await delay(500) |
||||||
|
}) |
||||||
|
|
||||||
|
it('balance renders', async function () { |
||||||
|
await delay(200) |
||||||
|
const balance = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > div.ether-balance.ether-balance-amount > div > div > div:nth-child(1) > div:nth-child(1)')) |
||||||
|
assert.equal(await balance.getText(), '100.000') |
||||||
|
await delay(200) |
||||||
|
}) |
||||||
|
|
||||||
|
it('sends transaction', async function () { |
||||||
|
const sendButton = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > button:nth-child(4)')) |
||||||
|
assert.equal(await sendButton.getText(), 'SEND') |
||||||
|
await sendButton.click() |
||||||
|
await delay(200) |
||||||
|
}) |
||||||
|
|
||||||
|
it('adds recipient address and amount', async function () { |
||||||
|
const sendTranscationScreen = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > h3:nth-child(2)')).getText() |
||||||
|
assert.equal(sendTranscationScreen, 'SEND TRANSACTION') |
||||||
|
const inputAddress = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(3) > div > input')) |
||||||
|
const inputAmmount = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(4) > input')) |
||||||
|
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') |
||||||
|
await inputAmmount.sendKeys('10') |
||||||
|
await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(4) > button')).click() |
||||||
|
await delay(300) |
||||||
|
}) |
||||||
|
|
||||||
|
it('confirms transaction', async function () { |
||||||
|
await delay(300) |
||||||
|
await driver.findElement(By.css('#pending-tx-form > div.flex-row.flex-space-around.conf-buttons > input')).click() |
||||||
|
await delay(500) |
||||||
|
}) |
||||||
|
|
||||||
|
it('finds the transaction in the transactions list', async function () { |
||||||
|
const tranasactionAmount = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > section > section > div > div > div > div.ether-balance.ether-balance-amount > div > div > div > div:nth-child(1)')) |
||||||
|
assert.equal(await tranasactionAmount.getText(), '10.0') |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Token Factory', function () { |
||||||
|
|
||||||
|
it('navigates to token factory', async function () { |
||||||
|
await driver.get('http://tokenfactory.surge.sh/') |
||||||
|
}) |
||||||
|
|
||||||
|
it('navigates to create token contract link', async function () { |
||||||
|
const createToken = await driver.findElement(By.css('#bs-example-navbar-collapse-1 > ul > li:nth-child(3) > a')) |
||||||
|
await createToken.click() |
||||||
|
}) |
||||||
|
|
||||||
|
it('adds input for token', async function () { |
||||||
|
const totalSupply = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(5) > input')) |
||||||
|
const tokenName = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(6) > input')) |
||||||
|
const tokenDecimal = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(7) > input')) |
||||||
|
const tokenSymbol = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(8) > input')) |
||||||
|
const createToken = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > button')) |
||||||
|
|
||||||
|
await totalSupply.sendKeys('100') |
||||||
|
await tokenName.sendKeys('Test') |
||||||
|
await tokenDecimal.sendKeys('0') |
||||||
|
await tokenSymbol.sendKeys('TST') |
||||||
|
await createToken.click() |
||||||
|
await delay(1000) |
||||||
|
}) |
||||||
|
|
||||||
|
it('confirms transaction in MetaMask popup', async function () { |
||||||
|
const windowHandles = await driver.getAllWindowHandles() |
||||||
|
await driver.switchTo().window(windowHandles[2]) |
||||||
|
const metamaskSubmit = await driver.findElement(By.css('#pending-tx-form > div.flex-row.flex-space-around.conf-buttons > input')) |
||||||
|
await metamaskSubmit.click() |
||||||
|
await delay(1000) |
||||||
|
}) |
||||||
|
|
||||||
|
it('switches back to Token Factory to grab the token contract address', async function () { |
||||||
|
const windowHandles = await driver.getAllWindowHandles() |
||||||
|
await driver.switchTo().window(windowHandles[0]) |
||||||
|
const tokenContactAddress = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > span:nth-child(3)')) |
||||||
|
tokenAddress = await tokenContactAddress.getText() |
||||||
|
await delay(500) |
||||||
|
}) |
||||||
|
|
||||||
|
it('navigates back to MetaMask popup in the tab', async function () { |
||||||
|
await driver.get(`chrome-extension://${extensionId}/popup.html`) |
||||||
|
await delay(700) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Add Token', function () { |
||||||
|
it('switches to the add token screen', async function () { |
||||||
|
const tokensTab = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section > div > div.inactiveForm.pointer')) |
||||||
|
assert.equal(await tokensTab.getText(), 'TOKENS') |
||||||
|
await tokensTab.click() |
||||||
|
await delay(300) |
||||||
|
}) |
||||||
|
|
||||||
|
it('navigates to the add token screen', async function () { |
||||||
|
const addTokenButton = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section > div.full-flex-height > div > button')) |
||||||
|
assert.equal(await addTokenButton.getText(), 'ADD TOKEN') |
||||||
|
await addTokenButton.click() |
||||||
|
}) |
||||||
|
|
||||||
|
it('checks add token screen rendered', async function () { |
||||||
|
const addTokenScreen = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.section-title.flex-row.flex-center > h2')) |
||||||
|
assert.equal(await addTokenScreen.getText(), 'ADD TOKEN') |
||||||
|
}) |
||||||
|
|
||||||
|
it('adds token parameters', async function () { |
||||||
|
const tokenContractAddress = await driver.findElement(By.css('#token-address')) |
||||||
|
await tokenContractAddress.sendKeys(tokenAddress) |
||||||
|
await delay(300) |
||||||
|
await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-justify-center.flex-grow.select-none > div > button')).click() |
||||||
|
await delay(100) |
||||||
|
}) |
||||||
|
|
||||||
|
it('checks the token balance', async function () { |
||||||
|
const tokenBalance = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > section > div.full-flex-height > ol > li:nth-child(2) > h3')) |
||||||
|
assert.equal(await tokenBalance.getText(), '100 TST') |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
async function getExtensionId () { |
||||||
|
const extension = await driver.executeScript('return document.querySelector("extensions-manager").shadowRoot.querySelector("extensions-view-manager extensions-item-list").shadowRoot.querySelector("#container > div.items-container > extensions-item:nth-child(2)").getAttribute("id")') |
||||||
|
return extension |
||||||
|
} |
||||||
|
|
||||||
|
async function setProviderType (type) { |
||||||
|
await driver.executeScript('window.metamask.setProviderType(arguments[0])', type) |
||||||
|
} |
||||||
|
|
||||||
|
async function verboseReportOnFailure (test) { |
||||||
|
const artifactDir = `./test-artifacts/chrome/${test.title}` |
||||||
|
const filepathBase = `${artifactDir}/test-failure` |
||||||
|
await pify(mkdirp)(artifactDir) |
||||||
|
// capture screenshot
|
||||||
|
const screenshot = await driver.takeScreenshot() |
||||||
|
await pify(fs.writeFile)(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' }) |
||||||
|
// capture dom source
|
||||||
|
const htmlSource = await driver.getPageSource() |
||||||
|
await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource) |
||||||
|
} |
||||||
|
|
||||||
|
}) |
@ -0,0 +1,322 @@ |
|||||||
|
const fs = require('fs') |
||||||
|
const mkdirp = require('mkdirp') |
||||||
|
const path = require('path') |
||||||
|
const assert = require('assert') |
||||||
|
const pify = require('pify') |
||||||
|
const webdriver = require('selenium-webdriver') |
||||||
|
const Command = require('selenium-webdriver/lib/command').Command |
||||||
|
const By = webdriver.By |
||||||
|
const { delay, buildFirefoxWebdriver } = require('../func') |
||||||
|
|
||||||
|
describe('', function () { |
||||||
|
let driver, accountAddress, tokenAddress, extensionId |
||||||
|
|
||||||
|
this.timeout(0) |
||||||
|
|
||||||
|
before(async function () { |
||||||
|
const extPath = path.resolve('dist/firefox') |
||||||
|
driver = buildFirefoxWebdriver() |
||||||
|
installWebExt(driver, extPath) |
||||||
|
await delay(700) |
||||||
|
}) |
||||||
|
|
||||||
|
afterEach(async function () { |
||||||
|
if (this.currentTest.state === 'failed') { |
||||||
|
await verboseReportOnFailure(this.currentTest) |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
after(async function () { |
||||||
|
await driver.quit() |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Setup', function () { |
||||||
|
|
||||||
|
it('switches to Firefox addon list', async function () { |
||||||
|
await driver.get('about:debugging#addons') |
||||||
|
await delay(1000) |
||||||
|
}) |
||||||
|
|
||||||
|
it(`selects MetaMask's extension id and opens it in the current tab`, async function () { |
||||||
|
const tabs = await driver.getAllWindowHandles() |
||||||
|
await driver.switchTo().window(tabs[0]) |
||||||
|
extensionId = await driver.findElement(By.css('dd.addon-target-info-content:nth-child(6) > span:nth-child(1)')).getText() |
||||||
|
await driver.get(`moz-extension://${extensionId}/popup.html`) |
||||||
|
await delay(500) |
||||||
|
}) |
||||||
|
|
||||||
|
it('sets provider type to localhost', async function () { |
||||||
|
await setProviderType('localhost') |
||||||
|
await delay(300) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Account Creation', () => { |
||||||
|
|
||||||
|
it('matches MetaMask title', async () => { |
||||||
|
const title = await driver.getTitle() |
||||||
|
assert.equal(title, 'MetaMask', 'title matches MetaMask') |
||||||
|
}) |
||||||
|
|
||||||
|
it('shows privacy notice', async () => { |
||||||
|
const privacy = await driver.findElement(By.css('.terms-header')).getText() |
||||||
|
assert.equal(privacy, 'PRIVACY NOTICE', 'shows privacy notice') |
||||||
|
await driver.findElement(By.css('button')).click() |
||||||
|
await delay(300) |
||||||
|
}) |
||||||
|
|
||||||
|
it('show terms of use', async () => { |
||||||
|
await delay(300) |
||||||
|
const terms = await driver.findElement(By.css('.terms-header')).getText() |
||||||
|
assert.equal(terms, 'TERMS OF USE', 'shows terms of use') |
||||||
|
await delay(300) |
||||||
|
}) |
||||||
|
|
||||||
|
it('checks if the TOU button is disabled', async () => { |
||||||
|
const button = await driver.findElement(By.css('button')).isEnabled() |
||||||
|
assert.equal(button, false, 'disabled continue button') |
||||||
|
const element = await driver.findElement(By.linkText('Attributions')) |
||||||
|
await driver.executeScript('arguments[0].scrollIntoView(true)', element) |
||||||
|
await delay(300) |
||||||
|
}) |
||||||
|
|
||||||
|
it('allows the button to be clicked when scrolled to the bottom of TOU', async () => { |
||||||
|
const button = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > button')) |
||||||
|
await delay(300) |
||||||
|
const buttonEnabled = await button.isEnabled() |
||||||
|
assert.equal(buttonEnabled, true, 'enabled continue button') |
||||||
|
await delay(200) |
||||||
|
await button.click() |
||||||
|
}) |
||||||
|
|
||||||
|
it('accepts password with length of eight', async () => { |
||||||
|
const passwordBox = await driver.findElement(By.id('password-box')) |
||||||
|
const passwordBoxConfirm = await driver.findElement(By.id('password-box-confirm')) |
||||||
|
const button = await driver.findElements(By.css('button')) |
||||||
|
|
||||||
|
await passwordBox.sendKeys('123456789') |
||||||
|
await passwordBoxConfirm.sendKeys('123456789') |
||||||
|
await button[0].click() |
||||||
|
await delay(500) |
||||||
|
}) |
||||||
|
|
||||||
|
it('shows value was created and seed phrase', async () => { |
||||||
|
await delay(300) |
||||||
|
const seedPhrase = await driver.findElement(By.css('.twelve-word-phrase')).getText() |
||||||
|
assert.equal(seedPhrase.split(' ').length, 12) |
||||||
|
const continueAfterSeedPhrase = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > button:nth-child(4)')) |
||||||
|
assert.equal(await continueAfterSeedPhrase.getText(), `I'VE COPIED IT SOMEWHERE SAFE`) |
||||||
|
await continueAfterSeedPhrase.click() |
||||||
|
await delay(300) |
||||||
|
}) |
||||||
|
|
||||||
|
it('shows account address', async function () { |
||||||
|
accountAddress = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div > div:nth-child(1) > flex-column > div.flex-row > div')).getText() |
||||||
|
}) |
||||||
|
|
||||||
|
it('logs out of the vault', async () => { |
||||||
|
await driver.findElement(By.css('.sandwich-expando')).click() |
||||||
|
await delay(500) |
||||||
|
const logoutButton = await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)')) |
||||||
|
assert.equal(await logoutButton.getText(), 'Log Out') |
||||||
|
await logoutButton.click() |
||||||
|
}) |
||||||
|
|
||||||
|
it('accepts account password after lock', async () => { |
||||||
|
await delay(500) |
||||||
|
await driver.findElement(By.id('password-box')).sendKeys('123456789') |
||||||
|
await driver.findElement(By.css('button')).click() |
||||||
|
await delay(500) |
||||||
|
}) |
||||||
|
|
||||||
|
it('shows QR code option', async () => { |
||||||
|
await delay(300) |
||||||
|
await driver.findElement(By.css('.fa-ellipsis-h')).click() |
||||||
|
await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div:nth-child(1) > flex-column > div.name-label > div > span > i > div > div > li:nth-child(3)')).click() |
||||||
|
await delay(300) |
||||||
|
}) |
||||||
|
|
||||||
|
it('checks QR code address is the same as account details address', async () => { |
||||||
|
const QRaccountAddress = await driver.findElement(By.css('.ellip-address')).getText() |
||||||
|
assert.equal(accountAddress.toLowerCase(), QRaccountAddress) |
||||||
|
await driver.findElement(By.css('.fa-arrow-left')).click() |
||||||
|
await delay(500) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Import Ganache seed phrase', function () { |
||||||
|
it('logs out', async function () { |
||||||
|
await driver.findElement(By.css('.sandwich-expando')).click() |
||||||
|
await delay(200) |
||||||
|
const logOut = await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)')) |
||||||
|
assert.equal(await logOut.getText(), 'Log Out') |
||||||
|
await logOut.click() |
||||||
|
await delay(300) |
||||||
|
}) |
||||||
|
|
||||||
|
it('restores from seed phrase', async function () { |
||||||
|
const restoreSeedLink = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div.flex-row.flex-center.flex-grow > p')) |
||||||
|
assert.equal(await restoreSeedLink.getText(), 'Restore from seed phrase') |
||||||
|
await restoreSeedLink.click() |
||||||
|
await delay(100) |
||||||
|
}) |
||||||
|
|
||||||
|
it('adds seed phrase', async function () { |
||||||
|
const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent' |
||||||
|
const seedTextArea = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > textarea')) |
||||||
|
await seedTextArea.sendKeys(testSeedPhrase) |
||||||
|
|
||||||
|
await driver.findElement(By.id('password-box')).sendKeys('123456789') |
||||||
|
await driver.findElement(By.id('password-box-confirm')).sendKeys('123456789') |
||||||
|
await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div > button:nth-child(2)')).click() |
||||||
|
await delay(500) |
||||||
|
}) |
||||||
|
|
||||||
|
it('balance renders', async function () { |
||||||
|
await delay(200) |
||||||
|
const balance = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > div.ether-balance.ether-balance-amount > div > div > div:nth-child(1) > div:nth-child(1)')) |
||||||
|
assert.equal(await balance.getText(), '100.000') |
||||||
|
await delay(200) |
||||||
|
}) |
||||||
|
|
||||||
|
it('sends transaction', async function () { |
||||||
|
const sendButton = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > button:nth-child(4)')) |
||||||
|
assert.equal(await sendButton.getText(), 'SEND') |
||||||
|
await sendButton.click() |
||||||
|
await delay(200) |
||||||
|
}) |
||||||
|
|
||||||
|
it('adds recipient address and amount', async function () { |
||||||
|
const sendTranscationScreen = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > h3:nth-child(2)')).getText() |
||||||
|
assert.equal(sendTranscationScreen, 'SEND TRANSACTION') |
||||||
|
const inputAddress = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(3) > div > input')) |
||||||
|
const inputAmmount = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(4) > input')) |
||||||
|
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') |
||||||
|
await inputAmmount.sendKeys('10') |
||||||
|
await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(4) > button')).click() |
||||||
|
await delay(300) |
||||||
|
}) |
||||||
|
|
||||||
|
it('confirms transaction', async function () { |
||||||
|
await delay(300) |
||||||
|
await driver.findElement(By.css('#pending-tx-form > div.flex-row.flex-space-around.conf-buttons > input')).click() |
||||||
|
await delay(500) |
||||||
|
}) |
||||||
|
|
||||||
|
it('finds the transaction in the transactions list', async function () { |
||||||
|
const tranasactionAmount = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > section > section > div > div > div > div.ether-balance.ether-balance-amount > div > div > div > div:nth-child(1)')) |
||||||
|
assert.equal(await tranasactionAmount.getText(), '10.0') |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Token Factory', function () { |
||||||
|
|
||||||
|
it('navigates to token factory', async function () { |
||||||
|
await driver.get('http://tokenfactory.surge.sh/') |
||||||
|
}) |
||||||
|
|
||||||
|
it('navigates to create token contract link', async function () { |
||||||
|
const createToken = await driver.findElement(By.css('#bs-example-navbar-collapse-1 > ul > li:nth-child(3) > a')) |
||||||
|
await createToken.click() |
||||||
|
}) |
||||||
|
|
||||||
|
it('adds input for token', async function () { |
||||||
|
const totalSupply = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(5) > input')) |
||||||
|
const tokenName = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(6) > input')) |
||||||
|
const tokenDecimal = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(7) > input')) |
||||||
|
const tokenSymbol = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(8) > input')) |
||||||
|
const createToken = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > button')) |
||||||
|
|
||||||
|
await totalSupply.sendKeys('100') |
||||||
|
await tokenName.sendKeys('Test') |
||||||
|
await tokenDecimal.sendKeys('0') |
||||||
|
await tokenSymbol.sendKeys('TST') |
||||||
|
await createToken.click() |
||||||
|
await delay(1000) |
||||||
|
}) |
||||||
|
|
||||||
|
// There is an issue with blank confirmation window, but the button is still there and the driver is able to clicked (?.?)
|
||||||
|
it('confirms transaction in MetaMask popup', async function () { |
||||||
|
const windowHandles = await driver.getAllWindowHandles() |
||||||
|
await driver.switchTo().window(windowHandles[2]) |
||||||
|
const metamaskSubmit = await driver.findElement(By.css('#pending-tx-form > div.flex-row.flex-space-around.conf-buttons > input')) |
||||||
|
await metamaskSubmit.click() |
||||||
|
await delay(1000) |
||||||
|
}) |
||||||
|
|
||||||
|
it('switches back to Token Factory to grab the token contract address', async function () { |
||||||
|
const windowHandles = await driver.getAllWindowHandles() |
||||||
|
await driver.switchTo().window(windowHandles[0]) |
||||||
|
const tokenContactAddress = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > span:nth-child(3)')) |
||||||
|
tokenAddress = await tokenContactAddress.getText() |
||||||
|
await delay(500) |
||||||
|
}) |
||||||
|
|
||||||
|
it('navigates back to MetaMask popup in the tab', async function () { |
||||||
|
await driver.get(`moz-extension://${extensionId}/popup.html`) |
||||||
|
await delay(700) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('Add Token', function () { |
||||||
|
it('switches to the add token screen', async function () { |
||||||
|
const tokensTab = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section > div > div.inactiveForm.pointer')) |
||||||
|
assert.equal(await tokensTab.getText(), 'TOKENS') |
||||||
|
await tokensTab.click() |
||||||
|
await delay(300) |
||||||
|
}) |
||||||
|
|
||||||
|
it('navigates to the add token screen', async function () { |
||||||
|
const addTokenButton = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section > div.full-flex-height > div > button')) |
||||||
|
assert.equal(await addTokenButton.getText(), 'ADD TOKEN') |
||||||
|
await addTokenButton.click() |
||||||
|
}) |
||||||
|
|
||||||
|
it('checks add token screen rendered', async function () { |
||||||
|
const addTokenScreen = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.section-title.flex-row.flex-center > h2')) |
||||||
|
assert.equal(await addTokenScreen.getText(), 'ADD TOKEN') |
||||||
|
}) |
||||||
|
|
||||||
|
it('adds token parameters', async function () { |
||||||
|
const tokenContractAddress = await driver.findElement(By.css('#token-address')) |
||||||
|
await tokenContractAddress.sendKeys(tokenAddress) |
||||||
|
await delay(300) |
||||||
|
await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-justify-center.flex-grow.select-none > div > button')).click() |
||||||
|
await delay(100) |
||||||
|
}) |
||||||
|
|
||||||
|
it('checks the token balance', async function () { |
||||||
|
const tokenBalance = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > section > div.full-flex-height > ol > li:nth-child(2) > h3')) |
||||||
|
assert.equal(await tokenBalance.getText(), '100 TST') |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
async function setProviderType(type) { |
||||||
|
await driver.executeScript('window.metamask.setProviderType(arguments[0])', type) |
||||||
|
} |
||||||
|
|
||||||
|
async function verboseReportOnFailure(test) { |
||||||
|
const artifactDir = `./test-artifacts/firefox/${test.title}` |
||||||
|
const filepathBase = `${artifactDir}/test-failure` |
||||||
|
await pify(mkdirp)(artifactDir) |
||||||
|
// capture screenshot
|
||||||
|
const screenshot = await driver.takeScreenshot() |
||||||
|
await pify(fs.writeFile)(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' }) |
||||||
|
// capture dom source
|
||||||
|
const htmlSource = await driver.getPageSource() |
||||||
|
await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource) |
||||||
|
} |
||||||
|
|
||||||
|
}) |
||||||
|
|
||||||
|
async function installWebExt (driver, extension) { |
||||||
|
const cmd = await new Command('moz-install-web-ext') |
||||||
|
.setParameter('path', path.resolve(extension)) |
||||||
|
.setParameter('temporary', true) |
||||||
|
|
||||||
|
await driver.getExecutor() |
||||||
|
.defineCommand(cmd.getName(), 'POST', '/session/:sessionId/moz/addon/install') |
||||||
|
|
||||||
|
return await driver.schedule(cmd, 'installWebExt(' + extension + ')') |
||||||
|
} |
||||||
|
|
@ -1,145 +0,0 @@ |
|||||||
const fs = require('fs') |
|
||||||
const mkdirp = require('mkdirp') |
|
||||||
const path = require('path') |
|
||||||
const assert = require('assert') |
|
||||||
const pify = require('pify') |
|
||||||
const webdriver = require('selenium-webdriver') |
|
||||||
const By = webdriver.By |
|
||||||
const { delay, buildWebDriver } = require('./func') |
|
||||||
|
|
||||||
describe('Metamask popup page', function () { |
|
||||||
let driver |
|
||||||
this.seedPhase |
|
||||||
this.accountAddress |
|
||||||
this.timeout(0) |
|
||||||
|
|
||||||
before(async function () { |
|
||||||
const extPath = path.resolve('dist/chrome') |
|
||||||
driver = buildWebDriver(extPath) |
|
||||||
await driver.get('chrome://extensions-frame') |
|
||||||
const elems = await driver.findElements(By.css('.extension-list-item-wrapper')) |
|
||||||
const extensionId = await elems[1].getAttribute('id') |
|
||||||
await driver.get(`chrome-extension://${extensionId}/popup.html`) |
|
||||||
await delay(500) |
|
||||||
}) |
|
||||||
|
|
||||||
afterEach(async function () { |
|
||||||
if (this.currentTest.state === 'failed') { |
|
||||||
await verboseReportOnFailure(this.currentTest) |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
after(async function () { |
|
||||||
await driver.quit() |
|
||||||
}) |
|
||||||
|
|
||||||
describe('#onboarding', () => { |
|
||||||
it('should open Metamask.io', async function () { |
|
||||||
const tabs = await driver.getAllWindowHandles() |
|
||||||
await driver.switchTo().window(tabs[0]) |
|
||||||
await delay(300) |
|
||||||
await setProviderType('localhost') |
|
||||||
await delay(300) |
|
||||||
}) |
|
||||||
|
|
||||||
it('should match title', async () => { |
|
||||||
const title = await driver.getTitle() |
|
||||||
assert.equal(title, 'MetaMask', 'title matches MetaMask') |
|
||||||
}) |
|
||||||
|
|
||||||
it('should show privacy notice', async () => { |
|
||||||
const privacy = await driver.findElement(By.css('.terms-header')).getText() |
|
||||||
assert.equal(privacy, 'PRIVACY NOTICE', 'shows privacy notice') |
|
||||||
driver.findElement(By.css('button')).click() |
|
||||||
await delay(300) |
|
||||||
}) |
|
||||||
|
|
||||||
it('should show terms of use', async () => { |
|
||||||
await delay(300) |
|
||||||
const terms = await driver.findElement(By.css('.terms-header')).getText() |
|
||||||
assert.equal(terms, 'TERMS OF USE', 'shows terms of use') |
|
||||||
await delay(300) |
|
||||||
}) |
|
||||||
|
|
||||||
it('should be unable to continue without scolling throught the terms of use', async () => { |
|
||||||
const button = await driver.findElement(By.css('button')).isEnabled() |
|
||||||
assert.equal(button, false, 'disabled continue button') |
|
||||||
const element = driver.findElement(By.linkText( |
|
||||||
'Attributions' |
|
||||||
)) |
|
||||||
await driver.executeScript('arguments[0].scrollIntoView(true)', element) |
|
||||||
await delay(300) |
|
||||||
}) |
|
||||||
|
|
||||||
it('should be able to continue when scrolled to the bottom of terms of use', async () => { |
|
||||||
const button = await driver.findElement(By.css('button')) |
|
||||||
const buttonEnabled = await button.isEnabled() |
|
||||||
await delay(500) |
|
||||||
assert.equal(buttonEnabled, true, 'enabled continue button') |
|
||||||
await button.click() |
|
||||||
await delay(300) |
|
||||||
}) |
|
||||||
|
|
||||||
it('should accept password with length of eight', async () => { |
|
||||||
const passwordBox = await driver.findElement(By.id('password-box')) |
|
||||||
const passwordBoxConfirm = await driver.findElement(By.id('password-box-confirm')) |
|
||||||
const button = driver.findElement(By.css('button')) |
|
||||||
|
|
||||||
passwordBox.sendKeys('123456789') |
|
||||||
passwordBoxConfirm.sendKeys('123456789') |
|
||||||
await delay(500) |
|
||||||
await button.click() |
|
||||||
}) |
|
||||||
|
|
||||||
it('should show value was created and seed phrase', async () => { |
|
||||||
await delay(700) |
|
||||||
this.seedPhase = await driver.findElement(By.css('.twelve-word-phrase')).getText() |
|
||||||
const continueAfterSeedPhrase = await driver.findElement(By.css('button')) |
|
||||||
await continueAfterSeedPhrase.click() |
|
||||||
await delay(300) |
|
||||||
}) |
|
||||||
|
|
||||||
it('should show lock account', async () => { |
|
||||||
await driver.findElement(By.css('.sandwich-expando')).click() |
|
||||||
await delay(500) |
|
||||||
await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)')).click() |
|
||||||
}) |
|
||||||
|
|
||||||
it('should accept account password after lock', async () => { |
|
||||||
await delay(500) |
|
||||||
await driver.findElement(By.id('password-box')).sendKeys('123456789') |
|
||||||
await driver.findElement(By.css('button')).click() |
|
||||||
await delay(500) |
|
||||||
}) |
|
||||||
|
|
||||||
it('should show QR code option', async () => { |
|
||||||
await delay(300) |
|
||||||
await driver.findElement(By.css('.fa-ellipsis-h')).click() |
|
||||||
await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div:nth-child(1) > flex-column > div.name-label > div > span > i > div > div > li:nth-child(3)')).click() |
|
||||||
await delay(300) |
|
||||||
}) |
|
||||||
|
|
||||||
it('should show the account address', async () => { |
|
||||||
this.accountAddress = await driver.findElement(By.css('.ellip-address')).getText() |
|
||||||
await driver.findElement(By.css('.fa-arrow-left')).click() |
|
||||||
await delay(500) |
|
||||||
}) |
|
||||||
}) |
|
||||||
|
|
||||||
async function setProviderType(type) { |
|
||||||
await driver.executeScript('window.metamask.setProviderType(arguments[0])', type) |
|
||||||
} |
|
||||||
|
|
||||||
async function verboseReportOnFailure(test) { |
|
||||||
const artifactDir = `./test-artifacts/${test.title}` |
|
||||||
const filepathBase = `${artifactDir}/test-failure` |
|
||||||
await pify(mkdirp)(artifactDir) |
|
||||||
// capture screenshot
|
|
||||||
const screenshot = await driver.takeScreenshot() |
|
||||||
await pify(fs.writeFile)(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' }) |
|
||||||
// capture dom source
|
|
||||||
const htmlSource = await driver.getPageSource() |
|
||||||
await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource) |
|
||||||
} |
|
||||||
|
|
||||||
}) |
|
@ -1,14 +1,77 @@ |
|||||||
const assert = require('assert') |
const assert = require('assert') |
||||||
const TxGasUtils = require('../../app/scripts/lib/tx-gas-utils') |
const Transaction = require('ethereumjs-tx') |
||||||
const { createTestProviderTools } = require('../stub/provider') |
const BN = require('bn.js') |
||||||
|
|
||||||
describe('Tx Gas Util', function () { |
|
||||||
let txGasUtil, provider, providerResultStub |
const { hexToBn, bnToHex } = require('../../app/scripts/lib/util') |
||||||
beforeEach(function () { |
const TxUtils = require('../../app/scripts/controllers/transactions/tx-gas-utils') |
||||||
providerResultStub = {} |
|
||||||
provider = createTestProviderTools({ scaffold: providerResultStub }).provider |
|
||||||
txGasUtil = new TxGasUtils({ |
describe('txUtils', function () { |
||||||
provider, |
let txUtils |
||||||
|
|
||||||
|
before(function () { |
||||||
|
txUtils = new TxUtils(new Proxy({}, { |
||||||
|
get: (obj, name) => { |
||||||
|
return () => {} |
||||||
|
}, |
||||||
|
})) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('chain Id', function () { |
||||||
|
it('prepares a transaction with the provided chainId', function () { |
||||||
|
const txParams = { |
||||||
|
to: '0x70ad465e0bab6504002ad58c744ed89c7da38524', |
||||||
|
from: '0x69ad465e0bab6504002ad58c744ed89c7da38525', |
||||||
|
value: '0x0', |
||||||
|
gas: '0x7b0c', |
||||||
|
gasPrice: '0x199c82cc00', |
||||||
|
data: '0x', |
||||||
|
nonce: '0x3', |
||||||
|
chainId: 42, |
||||||
|
} |
||||||
|
const ethTx = new Transaction(txParams) |
||||||
|
assert.equal(ethTx.getChainId(), 42, 'chainId is set from tx params') |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('addGasBuffer', function () { |
||||||
|
it('multiplies by 1.5, when within block gas limit', function () { |
||||||
|
// naive estimatedGas: 0x16e360 (1.5 mil)
|
||||||
|
const inputHex = '0x16e360' |
||||||
|
// dummy gas limit: 0x3d4c52 (4 mil)
|
||||||
|
const blockGasLimitHex = '0x3d4c52' |
||||||
|
const output = txUtils.addGasBuffer(inputHex, blockGasLimitHex) |
||||||
|
const inputBn = hexToBn(inputHex) |
||||||
|
const outputBn = hexToBn(output) |
||||||
|
const expectedBn = inputBn.muln(1.5) |
||||||
|
assert(outputBn.eq(expectedBn), 'returns 1.5 the input value') |
||||||
|
}) |
||||||
|
|
||||||
|
it('uses original estimatedGas, when above block gas limit', function () { |
||||||
|
// naive estimatedGas: 0x16e360 (1.5 mil)
|
||||||
|
const inputHex = '0x16e360' |
||||||
|
// dummy gas limit: 0x0f4240 (1 mil)
|
||||||
|
const blockGasLimitHex = '0x0f4240' |
||||||
|
const output = txUtils.addGasBuffer(inputHex, blockGasLimitHex) |
||||||
|
// const inputBn = hexToBn(inputHex)
|
||||||
|
const outputBn = hexToBn(output) |
||||||
|
const expectedBn = hexToBn(inputHex) |
||||||
|
assert(outputBn.eq(expectedBn), 'returns the original estimatedGas value') |
||||||
|
}) |
||||||
|
|
||||||
|
it('buffers up to recommend gas limit recommended ceiling', function () { |
||||||
|
// naive estimatedGas: 0x16e360 (1.5 mil)
|
||||||
|
const inputHex = '0x16e360' |
||||||
|
// dummy gas limit: 0x1e8480 (2 mil)
|
||||||
|
const blockGasLimitHex = '0x1e8480' |
||||||
|
const blockGasLimitBn = hexToBn(blockGasLimitHex) |
||||||
|
const ceilGasLimitBn = blockGasLimitBn.muln(0.9) |
||||||
|
const output = txUtils.addGasBuffer(inputHex, blockGasLimitHex) |
||||||
|
// const inputBn = hexToBn(inputHex)
|
||||||
|
// const outputBn = hexToBn(output)
|
||||||
|
const expectedHex = bnToHex(ceilGasLimitBn) |
||||||
|
assert.equal(output, expectedHex, 'returns the gas limit recommended ceiling value') |
||||||
}) |
}) |
||||||
}) |
}) |
||||||
}) |
}) |
||||||
|
@ -1,77 +1,98 @@ |
|||||||
const assert = require('assert') |
const assert = require('assert') |
||||||
const Transaction = require('ethereumjs-tx') |
const txUtils = require('../../app/scripts/controllers/transactions/lib/util') |
||||||
const BN = require('bn.js') |
|
||||||
|
|
||||||
|
|
||||||
const { hexToBn, bnToHex } = require('../../app/scripts/lib/util') |
|
||||||
const TxUtils = require('../../app/scripts/lib/tx-gas-utils') |
|
||||||
|
|
||||||
|
|
||||||
describe('txUtils', function () { |
describe('txUtils', function () { |
||||||
let txUtils |
describe('#validateTxParams', function () { |
||||||
|
it('does not throw for positive values', function () { |
||||||
before(function () { |
var sample = { |
||||||
txUtils = new TxUtils(new Proxy({}, { |
from: '0x1678a085c290ebd122dc42cba69373b5953b831d', |
||||||
get: (obj, name) => { |
value: '0x01', |
||||||
return () => {} |
} |
||||||
}, |
txUtils.validateTxParams(sample) |
||||||
})) |
}) |
||||||
}) |
|
||||||
|
|
||||||
describe('chain Id', function () { |
it('returns error for negative values', function () { |
||||||
it('prepares a transaction with the provided chainId', function () { |
var sample = { |
||||||
const txParams = { |
from: '0x1678a085c290ebd122dc42cba69373b5953b831d', |
||||||
to: '0x70ad465e0bab6504002ad58c744ed89c7da38524', |
value: '-0x01', |
||||||
from: '0x69ad465e0bab6504002ad58c744ed89c7da38525', |
} |
||||||
value: '0x0', |
try { |
||||||
gas: '0x7b0c', |
txUtils.validateTxParams(sample) |
||||||
gasPrice: '0x199c82cc00', |
} catch (err) { |
||||||
data: '0x', |
assert.ok(err, 'error') |
||||||
nonce: '0x3', |
|
||||||
chainId: 42, |
|
||||||
} |
} |
||||||
const ethTx = new Transaction(txParams) |
|
||||||
assert.equal(ethTx.getChainId(), 42, 'chainId is set from tx params') |
|
||||||
}) |
}) |
||||||
}) |
}) |
||||||
|
|
||||||
describe('addGasBuffer', function () { |
describe('#normalizeTxParams', () => { |
||||||
it('multiplies by 1.5, when within block gas limit', function () { |
it('should normalize txParams', () => { |
||||||
// naive estimatedGas: 0x16e360 (1.5 mil)
|
let txParams = { |
||||||
const inputHex = '0x16e360' |
chainId: '0x1', |
||||||
// dummy gas limit: 0x3d4c52 (4 mil)
|
from: 'a7df1beDBF813f57096dF77FCd515f0B3900e402', |
||||||
const blockGasLimitHex = '0x3d4c52' |
to: null, |
||||||
const output = txUtils.addGasBuffer(inputHex, blockGasLimitHex) |
data: '68656c6c6f20776f726c64', |
||||||
const inputBn = hexToBn(inputHex) |
random: 'hello world', |
||||||
const outputBn = hexToBn(output) |
} |
||||||
const expectedBn = inputBn.muln(1.5) |
|
||||||
assert(outputBn.eq(expectedBn), 'returns 1.5 the input value') |
let normalizedTxParams = txUtils.normalizeTxParams(txParams) |
||||||
|
|
||||||
|
assert(!normalizedTxParams.chainId, 'their should be no chainId') |
||||||
|
assert(!normalizedTxParams.to, 'their should be no to address if null') |
||||||
|
assert.equal(normalizedTxParams.from.slice(0, 2), '0x', 'from should be hexPrefixd') |
||||||
|
assert.equal(normalizedTxParams.data.slice(0, 2), '0x', 'data should be hexPrefixd') |
||||||
|
assert(!('random' in normalizedTxParams), 'their should be no random key in normalizedTxParams') |
||||||
|
|
||||||
|
txParams.to = 'a7df1beDBF813f57096dF77FCd515f0B3900e402' |
||||||
|
normalizedTxParams = txUtils.normalizeTxParams(txParams) |
||||||
|
assert.equal(normalizedTxParams.to.slice(0, 2), '0x', 'to should be hexPrefixd') |
||||||
|
|
||||||
}) |
}) |
||||||
|
}) |
||||||
|
|
||||||
it('uses original estimatedGas, when above block gas limit', function () { |
describe('#validateRecipient', () => { |
||||||
// naive estimatedGas: 0x16e360 (1.5 mil)
|
it('removes recipient for txParams with 0x when contract data is provided', function () { |
||||||
const inputHex = '0x16e360' |
const zeroRecipientandDataTxParams = { |
||||||
// dummy gas limit: 0x0f4240 (1 mil)
|
from: '0x1678a085c290ebd122dc42cba69373b5953b831d', |
||||||
const blockGasLimitHex = '0x0f4240' |
to: '0x', |
||||||
const output = txUtils.addGasBuffer(inputHex, blockGasLimitHex) |
data: 'bytecode', |
||||||
// const inputBn = hexToBn(inputHex)
|
} |
||||||
const outputBn = hexToBn(output) |
const sanitizedTxParams = txUtils.validateRecipient(zeroRecipientandDataTxParams) |
||||||
const expectedBn = hexToBn(inputHex) |
assert.deepEqual(sanitizedTxParams, { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', data: 'bytecode' }, 'no recipient with 0x') |
||||||
assert(outputBn.eq(expectedBn), 'returns the original estimatedGas value') |
|
||||||
}) |
}) |
||||||
|
|
||||||
it('buffers up to recommend gas limit recommended ceiling', function () { |
it('should error when recipient is 0x', function () { |
||||||
// naive estimatedGas: 0x16e360 (1.5 mil)
|
const zeroRecipientTxParams = { |
||||||
const inputHex = '0x16e360' |
from: '0x1678a085c290ebd122dc42cba69373b5953b831d', |
||||||
// dummy gas limit: 0x1e8480 (2 mil)
|
to: '0x', |
||||||
const blockGasLimitHex = '0x1e8480' |
} |
||||||
const blockGasLimitBn = hexToBn(blockGasLimitHex) |
assert.throws(() => { txUtils.validateRecipient(zeroRecipientTxParams) }, Error, 'Invalid recipient address') |
||||||
const ceilGasLimitBn = blockGasLimitBn.muln(0.9) |
|
||||||
const output = txUtils.addGasBuffer(inputHex, blockGasLimitHex) |
|
||||||
// const inputBn = hexToBn(inputHex)
|
|
||||||
// const outputBn = hexToBn(output)
|
|
||||||
const expectedHex = bnToHex(ceilGasLimitBn) |
|
||||||
assert.equal(output, expectedHex, 'returns the gas limit recommended ceiling value') |
|
||||||
}) |
}) |
||||||
}) |
}) |
||||||
|
|
||||||
|
|
||||||
|
describe('#validateFrom', () => { |
||||||
|
it('should error when from is not a hex string', function () { |
||||||
|
|
||||||
|
// where from is undefined
|
||||||
|
const txParams = {} |
||||||
|
assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`) |
||||||
|
|
||||||
|
// where from is array
|
||||||
|
txParams.from = [] |
||||||
|
assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`) |
||||||
|
|
||||||
|
// where from is a object
|
||||||
|
txParams.from = {} |
||||||
|
assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`) |
||||||
|
|
||||||
|
// where from is a invalid address
|
||||||
|
txParams.from = 'im going to fail' |
||||||
|
assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address`) |
||||||
|
|
||||||
|
// should run
|
||||||
|
txParams.from ='0x1678a085c290ebd122dc42cba69373b5953b831d' |
||||||
|
txUtils.validateFrom(txParams) |
||||||
|
}) |
||||||
|
}) |
||||||
}) |
}) |
@ -0,0 +1,106 @@ |
|||||||
|
import React, { Component } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import classnames from 'classnames' |
||||||
|
|
||||||
|
const { ENVIRONMENT_TYPE_NOTIFICATION } = require('../../../../app/scripts/lib/enums') |
||||||
|
const { DEFAULT_ROUTE, CONFIRM_TRANSACTION_ROUTE } = require('../../routes') |
||||||
|
const Identicon = require('../identicon') |
||||||
|
const NetworkIndicator = require('../network') |
||||||
|
|
||||||
|
class AppHeader extends Component { |
||||||
|
static propTypes = { |
||||||
|
history: PropTypes.object, |
||||||
|
location: PropTypes.object, |
||||||
|
network: PropTypes.string, |
||||||
|
provider: PropTypes.object, |
||||||
|
networkDropdownOpen: PropTypes.bool, |
||||||
|
showNetworkDropdown: PropTypes.func, |
||||||
|
hideNetworkDropdown: PropTypes.func, |
||||||
|
toggleAccountMenu: PropTypes.func, |
||||||
|
selectedAddress: PropTypes.string, |
||||||
|
isUnlocked: PropTypes.bool, |
||||||
|
} |
||||||
|
|
||||||
|
static contextTypes = { |
||||||
|
t: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
handleNetworkIndicatorClick (event) { |
||||||
|
event.preventDefault() |
||||||
|
event.stopPropagation() |
||||||
|
|
||||||
|
const { networkDropdownOpen, showNetworkDropdown, hideNetworkDropdown } = this.props |
||||||
|
|
||||||
|
return networkDropdownOpen === false |
||||||
|
? showNetworkDropdown() |
||||||
|
: hideNetworkDropdown() |
||||||
|
} |
||||||
|
|
||||||
|
renderAccountMenu () { |
||||||
|
const { isUnlocked, toggleAccountMenu, selectedAddress } = this.props |
||||||
|
|
||||||
|
return isUnlocked && ( |
||||||
|
<div |
||||||
|
className="account-menu__icon" |
||||||
|
onClick={toggleAccountMenu} |
||||||
|
> |
||||||
|
<Identicon |
||||||
|
address={selectedAddress} |
||||||
|
diameter={32} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
const { |
||||||
|
network, |
||||||
|
provider, |
||||||
|
history, |
||||||
|
location, |
||||||
|
isUnlocked, |
||||||
|
} = this.props |
||||||
|
|
||||||
|
if (window.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION) { |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<div |
||||||
|
className={classnames('app-header', { 'app-header--back-drop': isUnlocked })}> |
||||||
|
<div className="app-header__contents"> |
||||||
|
<div |
||||||
|
className="app-header__logo-container" |
||||||
|
onClick={() => history.push(DEFAULT_ROUTE)} |
||||||
|
> |
||||||
|
<img |
||||||
|
className="app-header__metafox" |
||||||
|
src="/images/metamask-fox.svg" |
||||||
|
height={42} |
||||||
|
width={42} |
||||||
|
/> |
||||||
|
<div className="flex-row"> |
||||||
|
<h1>{ this.context.t('appName') }</h1> |
||||||
|
<div className="app-header__beta-label"> |
||||||
|
{ this.context.t('beta') } |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="app-header__account-menu-container"> |
||||||
|
<div className="network-component-wrapper"> |
||||||
|
<NetworkIndicator |
||||||
|
network={network} |
||||||
|
provider={provider} |
||||||
|
onClick={event => this.handleNetworkIndicatorClick(event)} |
||||||
|
disabled={location.pathname === CONFIRM_TRANSACTION_ROUTE} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
{ this.renderAccountMenu() } |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default AppHeader |
@ -0,0 +1,38 @@ |
|||||||
|
import { connect } from 'react-redux' |
||||||
|
import { withRouter } from 'react-router-dom' |
||||||
|
import { compose } from 'recompose' |
||||||
|
|
||||||
|
import AppHeader from './app-header.component' |
||||||
|
const actions = require('../../actions') |
||||||
|
|
||||||
|
const mapStateToProps = state => { |
||||||
|
const { appState, metamask } = state |
||||||
|
const { networkDropdownOpen } = appState |
||||||
|
const { |
||||||
|
network, |
||||||
|
provider, |
||||||
|
selectedAddress, |
||||||
|
isUnlocked, |
||||||
|
} = metamask |
||||||
|
|
||||||
|
return { |
||||||
|
networkDropdownOpen, |
||||||
|
network, |
||||||
|
provider, |
||||||
|
selectedAddress, |
||||||
|
isUnlocked, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => { |
||||||
|
return { |
||||||
|
showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()), |
||||||
|
hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()), |
||||||
|
toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default compose( |
||||||
|
withRouter, |
||||||
|
connect(mapStateToProps, mapDispatchToProps) |
||||||
|
)(AppHeader) |
@ -0,0 +1,2 @@ |
|||||||
|
import AppHeader from './app-header.container' |
||||||
|
module.exports = AppHeader |
@ -0,0 +1,43 @@ |
|||||||
|
const { Component } = require('react') |
||||||
|
const h = require('react-hyperscript') |
||||||
|
const PropTypes = require('prop-types') |
||||||
|
const classnames = require('classnames') |
||||||
|
|
||||||
|
const SECONDARY = 'secondary' |
||||||
|
const CLASSNAME_PRIMARY = 'btn-primary' |
||||||
|
const CLASSNAME_PRIMARY_LARGE = 'btn-primary--lg' |
||||||
|
const CLASSNAME_SECONDARY = 'btn-secondary' |
||||||
|
const CLASSNAME_SECONDARY_LARGE = 'btn-secondary--lg' |
||||||
|
|
||||||
|
const getClassName = (type, large = false) => { |
||||||
|
let output = type === SECONDARY ? CLASSNAME_SECONDARY : CLASSNAME_PRIMARY |
||||||
|
|
||||||
|
if (large) { |
||||||
|
output += ` ${type === SECONDARY ? CLASSNAME_SECONDARY_LARGE : CLASSNAME_PRIMARY_LARGE}` |
||||||
|
} |
||||||
|
|
||||||
|
return output |
||||||
|
} |
||||||
|
|
||||||
|
class Button extends Component { |
||||||
|
render () { |
||||||
|
const { type, large, className, ...buttonProps } = this.props |
||||||
|
|
||||||
|
return ( |
||||||
|
h('button', { |
||||||
|
className: classnames(getClassName(type, large), className), |
||||||
|
...buttonProps, |
||||||
|
}, this.props.children) |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Button.propTypes = { |
||||||
|
type: PropTypes.string, |
||||||
|
large: PropTypes.bool, |
||||||
|
className: PropTypes.string, |
||||||
|
children: PropTypes.string, |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = Button |
||||||
|
|
@ -0,0 +1,41 @@ |
|||||||
|
import React from 'react' |
||||||
|
import { storiesOf } from '@storybook/react' |
||||||
|
import { action } from '@storybook/addon-actions' |
||||||
|
import Button from './' |
||||||
|
import { text } from '@storybook/addon-knobs/react' |
||||||
|
|
||||||
|
storiesOf('Button', module) |
||||||
|
.add('primary', () => |
||||||
|
<Button |
||||||
|
onClick={action('clicked')} |
||||||
|
type="primary" |
||||||
|
> |
||||||
|
{text('text', 'Click me')} |
||||||
|
</Button> |
||||||
|
) |
||||||
|
.add('secondary', () => ( |
||||||
|
<Button |
||||||
|
onClick={action('clicked')} |
||||||
|
type="secondary" |
||||||
|
> |
||||||
|
{text('text', 'Click me')} |
||||||
|
</Button> |
||||||
|
)) |
||||||
|
.add('large primary', () => ( |
||||||
|
<Button |
||||||
|
onClick={action('clicked')} |
||||||
|
type="primary" |
||||||
|
large |
||||||
|
> |
||||||
|
{text('text', 'Click me')} |
||||||
|
</Button> |
||||||
|
)) |
||||||
|
.add('large secondary', () => ( |
||||||
|
<Button |
||||||
|
onClick={action('clicked')} |
||||||
|
type="secondary" |
||||||
|
large |
||||||
|
> |
||||||
|
{text('text', 'Click me')} |
||||||
|
</Button> |
||||||
|
)) |
@ -0,0 +1,2 @@ |
|||||||
|
const Button = require('./button.component') |
||||||
|
module.exports = Button |
@ -0,0 +1,45 @@ |
|||||||
|
const { Component } = require('react') |
||||||
|
const PropTypes = require('prop-types') |
||||||
|
const h = require('react-hyperscript') |
||||||
|
const copyToClipboard = require('copy-to-clipboard') |
||||||
|
const { exportAsFile } = require('../../util') |
||||||
|
|
||||||
|
class ExportTextContainer extends Component { |
||||||
|
render () { |
||||||
|
const { text = '', filename = '' } = this.props |
||||||
|
const { t } = this.context |
||||||
|
|
||||||
|
return ( |
||||||
|
h('.export-text-container', [ |
||||||
|
h('.export-text-container__text-container', [ |
||||||
|
h('.export-text-container__text', text), |
||||||
|
]), |
||||||
|
h('.export-text-container__buttons-container', [ |
||||||
|
h('.export-text-container__button.export-text-container__button--copy', { |
||||||
|
onClick: () => copyToClipboard(text), |
||||||
|
}, [ |
||||||
|
h('img', { src: 'images/copy-to-clipboard.svg' }), |
||||||
|
h('.export-text-container__button-text', t('copyToClipboard')), |
||||||
|
]), |
||||||
|
h('.export-text-container__button', { |
||||||
|
onClick: () => exportAsFile(filename, text), |
||||||
|
}, [ |
||||||
|
h('img', { src: 'images/download.svg' }), |
||||||
|
h('.export-text-container__button-text', t('saveAsCsvFile')), |
||||||
|
]), |
||||||
|
]), |
||||||
|
]) |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ExportTextContainer.propTypes = { |
||||||
|
text: PropTypes.string, |
||||||
|
filename: PropTypes.string, |
||||||
|
} |
||||||
|
|
||||||
|
ExportTextContainer.contextTypes = { |
||||||
|
t: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = ExportTextContainer |
@ -0,0 +1,52 @@ |
|||||||
|
.export-text-container { |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
flex-direction: column; |
||||||
|
align-items: center; |
||||||
|
border: 1px solid $alto; |
||||||
|
border-radius: 4px; |
||||||
|
font-weight: 400; |
||||||
|
|
||||||
|
&__text-container { |
||||||
|
width: 100%; |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
padding: 20px; |
||||||
|
border-radius: 4px; |
||||||
|
background: $alabaster; |
||||||
|
} |
||||||
|
|
||||||
|
&__text { |
||||||
|
resize: none; |
||||||
|
border: none; |
||||||
|
background: $alabaster; |
||||||
|
font-size: 20px; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
|
||||||
|
&__buttons-container { |
||||||
|
display: flex; |
||||||
|
flex-direction: row; |
||||||
|
border-top: 1px solid $alto; |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
&__button { |
||||||
|
padding: 10px; |
||||||
|
flex: 1; |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
font-size: 12px; |
||||||
|
cursor: pointer; |
||||||
|
color: $curious-blue; |
||||||
|
|
||||||
|
&--copy { |
||||||
|
border-right: 1px solid $alto; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&__button-text { |
||||||
|
padding-left: 10px; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,2 @@ |
|||||||
|
const ExportTextContainer = require('./export-text-container.component') |
||||||
|
module.exports = ExportTextContainer |
@ -0,0 +1,2 @@ |
|||||||
|
const LoadingScreen = require('./loading-screen.component') |
||||||
|
module.exports = LoadingScreen |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue