commit
a741cc4fc4
@ -1 +1,5 @@ |
||||
app/scripts/lib/extension-instance.js |
||||
test/integration/bundle.js |
||||
test/integration/jquery-3.1.0.min.js |
||||
test/integration/helpers.js |
||||
test/integration/lib/first-time.js |
@ -1,18 +1,27 @@ |
||||
dist |
||||
npm-debug.log |
||||
node_modules |
||||
temp |
||||
.tmp |
||||
.sass-cache |
||||
package-lock.json |
||||
|
||||
app/bower_components |
||||
test/bower_components |
||||
package |
||||
|
||||
temp |
||||
.tmp |
||||
.sass-cache |
||||
.DS_Store |
||||
app/.DS_Store |
||||
|
||||
dist |
||||
builds/ |
||||
disc/ |
||||
notes.txt |
||||
app/.DS_Store |
||||
development/bundle.js |
||||
builds.zip |
||||
test/integration/bundle.js |
||||
|
||||
development/bundle.js |
||||
development/states.js |
||||
test/integration/bundle.js |
||||
test/background.js |
||||
test/bundle.js |
||||
test/test-bundle.js |
||||
|
||||
notes.txt |
File diff suppressed because one or more lines are too long
@ -1,16 +1,15 @@ |
||||
const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask' |
||||
const TESTNET_RPC_URL = 'https://ropsten.infura.io/metamask' |
||||
const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask' |
||||
const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask' |
||||
const DEFAULT_RPC_URL = TESTNET_RPC_URL |
||||
const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask' |
||||
|
||||
global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' |
||||
|
||||
module.exports = { |
||||
network: { |
||||
default: DEFAULT_RPC_URL, |
||||
mainnet: MAINET_RPC_URL, |
||||
testnet: TESTNET_RPC_URL, |
||||
morden: TESTNET_RPC_URL, |
||||
ropsten: ROPSTEN_RPC_URL, |
||||
kovan: KOVAN_RPC_URL, |
||||
rinkeby: RINKEBY_RPC_URL, |
||||
}, |
||||
} |
||||
|
@ -0,0 +1,129 @@ |
||||
const EventEmitter = require('events') |
||||
const MetaMaskProvider = require('web3-provider-engine/zero.js') |
||||
const ObservableStore = require('obs-store') |
||||
const ComposedStore = require('obs-store/lib/composed') |
||||
const extend = require('xtend') |
||||
const EthQuery = require('eth-query') |
||||
const RPC_ADDRESS_LIST = require('../config.js').network |
||||
const DEFAULT_RPC = RPC_ADDRESS_LIST['rinkeby'] |
||||
|
||||
module.exports = class NetworkController extends EventEmitter { |
||||
constructor (config) { |
||||
super() |
||||
this.networkStore = new ObservableStore('loading') |
||||
config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider) |
||||
this.providerStore = new ObservableStore(config.provider) |
||||
this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore }) |
||||
this._providerListeners = {} |
||||
|
||||
this.on('networkDidChange', this.lookupNetwork) |
||||
this.providerStore.subscribe((state) => this.switchNetwork({rpcUrl: state.rpcTarget})) |
||||
} |
||||
|
||||
get provider () { |
||||
return this._proxy |
||||
} |
||||
|
||||
set provider (provider) { |
||||
this._provider = provider |
||||
} |
||||
|
||||
initializeProvider (opts) { |
||||
this.providerInit = opts |
||||
this._provider = MetaMaskProvider(opts) |
||||
this._proxy = new Proxy(this._provider, { |
||||
get: (obj, name) => { |
||||
if (name === 'on') return this._on.bind(this) |
||||
return this._provider[name] |
||||
}, |
||||
set: (obj, name, value) => { |
||||
this._provider[name] = value |
||||
}, |
||||
}) |
||||
this.provider.on('block', this._logBlock.bind(this)) |
||||
this.provider.on('error', this.verifyNetwork.bind(this)) |
||||
this.ethQuery = new EthQuery(this.provider) |
||||
this.lookupNetwork() |
||||
return this.provider |
||||
} |
||||
|
||||
switchNetwork (providerInit) { |
||||
this.setNetworkState('loading') |
||||
const newInit = extend(this.providerInit, providerInit) |
||||
this.providerInit = newInit |
||||
|
||||
this._provider.removeAllListeners() |
||||
this._provider.stop() |
||||
this.provider = MetaMaskProvider(newInit) |
||||
// apply the listners created by other controllers
|
||||
Object.keys(this._providerListeners).forEach((key) => { |
||||
this._providerListeners[key].forEach((handler) => this._provider.addListener(key, handler)) |
||||
}) |
||||
this.emit('networkDidChange') |
||||
} |
||||
|
||||
|
||||
verifyNetwork () { |
||||
// Check network when restoring connectivity:
|
||||
if (this.isNetworkLoading()) this.lookupNetwork() |
||||
} |
||||
|
||||
getNetworkState () { |
||||
return this.networkStore.getState() |
||||
} |
||||
|
||||
setNetworkState (network) { |
||||
return this.networkStore.putState(network) |
||||
} |
||||
|
||||
isNetworkLoading () { |
||||
return this.getNetworkState() === 'loading' |
||||
} |
||||
|
||||
lookupNetwork () { |
||||
this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => { |
||||
if (err) return this.setNetworkState('loading') |
||||
log.info('web3.getNetwork returned ' + network) |
||||
this.setNetworkState(network) |
||||
}) |
||||
} |
||||
|
||||
setRpcTarget (rpcUrl) { |
||||
this.providerStore.updateState({ |
||||
type: 'rpc', |
||||
rpcTarget: rpcUrl, |
||||
}) |
||||
} |
||||
|
||||
getCurrentRpcAddress () { |
||||
const provider = this.getProviderConfig() |
||||
if (!provider) return null |
||||
return this.getRpcAddressForType(provider.type) |
||||
} |
||||
|
||||
setProviderType (type) { |
||||
if (type === this.getProviderConfig().type) return |
||||
const rpcTarget = this.getRpcAddressForType(type) |
||||
this.providerStore.updateState({type, rpcTarget}) |
||||
} |
||||
|
||||
getProviderConfig () { |
||||
return this.providerStore.getState() |
||||
} |
||||
|
||||
getRpcAddressForType (type, provider = this.getProviderConfig()) { |
||||
if (RPC_ADDRESS_LIST[type]) return RPC_ADDRESS_LIST[type] |
||||
return provider && provider.rpcTarget ? provider.rpcTarget : DEFAULT_RPC |
||||
} |
||||
|
||||
_logBlock (block) { |
||||
log.info(`BLOCK CHANGED: #${block.number.toString('hex')} 0x${block.hash.toString('hex')}`) |
||||
this.verifyNetwork() |
||||
} |
||||
|
||||
_on (event, handler) { |
||||
if (!this._providerListeners[event]) this._providerListeners[event] = [] |
||||
this._providerListeners[event].push(handler) |
||||
this._provider.on(event, handler) |
||||
} |
||||
} |
@ -1,11 +1,15 @@ |
||||
// test and development environment variables
|
||||
const env = process.env.METAMASK_ENV |
||||
const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' |
||||
|
||||
//
|
||||
// The default state of MetaMask
|
||||
//
|
||||
|
||||
module.exports = { |
||||
config: { |
||||
config: {}, |
||||
NetworkController: { |
||||
provider: { |
||||
type: 'testnet', |
||||
type: (METAMASK_DEBUG || env === 'test') ? 'rinkeby' : 'mainnet', |
||||
}, |
||||
}, |
||||
} |
@ -0,0 +1,34 @@ |
||||
const version = 13 |
||||
|
||||
/* |
||||
|
||||
This migration modifies the network config from ambiguous 'testnet' to explicit 'ropsten' |
||||
|
||||
*/ |
||||
|
||||
const clone = require('clone') |
||||
|
||||
module.exports = { |
||||
version, |
||||
|
||||
migrate: function (originalVersionedData) { |
||||
const versionedData = clone(originalVersionedData) |
||||
versionedData.meta.version = version |
||||
try { |
||||
const state = versionedData.data |
||||
const newState = transformState(state) |
||||
versionedData.data = newState |
||||
} catch (err) { |
||||
console.warn(`MetaMask Migration #${version}` + err.stack) |
||||
} |
||||
return Promise.resolve(versionedData) |
||||
}, |
||||
} |
||||
|
||||
function transformState (state) { |
||||
const newState = state |
||||
if (newState.config.provider.type === 'testnet') { |
||||
newState.config.provider.type = 'ropsten' |
||||
} |
||||
return newState |
||||
} |
@ -0,0 +1,34 @@ |
||||
const version = 14 |
||||
|
||||
/* |
||||
|
||||
This migration removes provider from config and moves it too NetworkController. |
||||
|
||||
*/ |
||||
|
||||
const clone = require('clone') |
||||
|
||||
module.exports = { |
||||
version, |
||||
|
||||
migrate: function (originalVersionedData) { |
||||
const versionedData = clone(originalVersionedData) |
||||
versionedData.meta.version = version |
||||
try { |
||||
const state = versionedData.data |
||||
const newState = transformState(state) |
||||
versionedData.data = newState |
||||
} catch (err) { |
||||
console.warn(`MetaMask Migration #${version}` + err.stack) |
||||
} |
||||
return Promise.resolve(versionedData) |
||||
}, |
||||
} |
||||
|
||||
function transformState (state) { |
||||
const newState = state |
||||
newState.NetworkController = {} |
||||
newState.NetworkController.provider = newState.config.provider |
||||
delete newState.config.provider |
||||
return newState |
||||
} |
@ -0,0 +1,14 @@ |
||||
## Add Custom Build to Chrome |
||||
|
||||
Open `Settings` > `Extensions`. |
||||
|
||||
Check "Developer mode". |
||||
|
||||
At the top, click `Load Unpacked Extension`. |
||||
|
||||
Navigate to your `metamask-plugin/dist/chrome` folder. |
||||
|
||||
Click `Select`. |
||||
|
||||
You now have the plugin, and can click 'inspect views: background plugin' to view its dev console. |
||||
|
@ -0,0 +1,14 @@ |
||||
# Add Custom Build to Firefox |
||||
|
||||
Go to the url `about:debugging`. |
||||
|
||||
Click the button `Load Temporary Add-On`. |
||||
|
||||
Select the file `dist/firefox/manifest.json`. |
||||
|
||||
You can optionally enable debugging, and click `Debug`, for a console window that logs all of Metamask's processes to a single console. |
||||
|
||||
If you have problems debugging, try connecting to the IRC channel `#webextensions` on `irc.mozilla.org`. |
||||
|
||||
For longer questions, use the StackOverfow tag `firefox-addons`. |
||||
|
@ -0,0 +1,25 @@ |
||||
## Adding Custom Networks |
||||
|
||||
To add another network to our dropdown menu, make sure the following files are adjusted properly: |
||||
|
||||
``` |
||||
app/scripts/config.js |
||||
app/scripts/lib/buy-eth-url.js |
||||
app/scripts/lib/config-manager.js |
||||
ui/app/app.js |
||||
ui/app/components/buy-button-subview.js |
||||
ui/app/components/drop-menu-item.js |
||||
ui/app/components/network.js |
||||
ui/app/components/transaction-list-item.js |
||||
ui/app/config.js |
||||
ui/app/css/lib.css |
||||
ui/lib/account-link.js |
||||
ui/lib/explorer-link.js |
||||
``` |
||||
|
||||
You will need: |
||||
+ The network ID |
||||
+ An RPC Endpoint url |
||||
+ An explorer link |
||||
+ CSS for the display icon |
||||
|
@ -0,0 +1,10 @@ |
||||
### Developing on Dependencies |
||||
|
||||
To enjoy the live-reloading that `gulp dev` offers while working on the `web3-provider-engine` or other dependencies: |
||||
|
||||
1. Clone the dependency locally. |
||||
2. `npm install` in its folder. |
||||
3. Run `npm link` in its folder. |
||||
4. Run `npm link $DEP_NAME` in this project folder. |
||||
5. Next time you `npm start` it will watch the dependency for changes as well! |
||||
|
@ -0,0 +1,35 @@ |
||||
### Generate Development Visualization |
||||
|
||||
This will generate a video of the repo commit history. |
||||
|
||||
Install preqs: |
||||
``` |
||||
brew install gource |
||||
brew install ffmpeg |
||||
``` |
||||
|
||||
From the repo dir, pipe `gource` into `ffmpeg`: |
||||
``` |
||||
gource \ |
||||
--seconds-per-day .1 \ |
||||
--user-scale 1.5 \ |
||||
--default-user-image "./images/icon-512.png" \ |
||||
--viewport 1280x720 \ |
||||
--auto-skip-seconds .1 \ |
||||
--multi-sampling \ |
||||
--stop-at-end \ |
||||
--highlight-users \ |
||||
--hide mouse,progress \ |
||||
--file-idle-time 0 \ |
||||
--max-files 0 \ |
||||
--background-colour 000000 \ |
||||
--font-size 18 \ |
||||
--date-format "%b %d, %Y" \ |
||||
--highlight-dirs \ |
||||
--user-friction 0.1 \ |
||||
--title "MetaMask Development History" \ |
||||
--output-ppm-stream - \ |
||||
--output-framerate 30 \ |
||||
| ffmpeg -y -r 30 -f image2pipe -vcodec ppm -i - -b 65536K metamask-dev-history.mp4 |
||||
``` |
||||
|
@ -0,0 +1,15 @@ |
||||
## Generating Notices |
||||
|
||||
To add a notice: |
||||
``` |
||||
npm run generateNotice |
||||
``` |
||||
Enter the body of your notice into the text editor that pops up, without including the body. Be sure to save the file before closing the window! |
||||
Afterwards, enter the title of the notice in the command line and press enter. Afterwards, add and commit the new changes made. |
||||
|
||||
To delete a notice: |
||||
``` |
||||
npm run deleteNotice |
||||
``` |
||||
A list of active notices will pop up. Enter the corresponding id in the command line prompt and add and commit the new changes afterwards. |
||||
|
@ -0,0 +1,19 @@ |
||||
# Publishing Guide |
||||
|
||||
When publishing a new version of MetaMask, we follow this procedure: |
||||
|
||||
## Incrementing Version & Changelog |
||||
|
||||
You must be authorized already on the MetaMask plugin. |
||||
|
||||
1. Update the version in `app/manifest.json` and the Changelog in `CHANGELOG.md`. |
||||
2. Visit [the chrome developer dashboard](https://chrome.google.com/webstore/developer/dashboard?authuser=2). |
||||
|
||||
## Publishing |
||||
|
||||
1. `npm run dist` to generate the latest build. |
||||
2. Publish to chrome store. |
||||
3. Publish to firefox addon marketplace. |
||||
4. Post on Github releases page. |
||||
5. `npm run announce`, post that announcement in our public places. |
||||
|
@ -0,0 +1,6 @@ |
||||
# Running UI Dev Mode |
||||
|
||||
You can run `npm run ui`, and your browser should open a live-reloading demo version of the plugin UI. |
||||
|
||||
Some actions will crash the app, so this is only for tuning aesthetics, but it allows live-reloading styles, which is a much faster feedback loop than reloading the full extension. |
||||
|
@ -0,0 +1,8 @@ |
||||
### Developing on UI with Mocked Background Process |
||||
|
||||
You can run `npm run mock` and your browser should open a live-reloading demo version of the plugin UI, just like the `npm run ui`, except that it tries to actually perform all normal operations. |
||||
|
||||
It does not yet connect to a real blockchain (this could be a good test feature later, connecting to a test blockchain), so only local operations work. |
||||
|
||||
You can reset the mock ui at any time with the `Reset` button at the top of the screen. |
||||
|
@ -1,20 +1,33 @@ |
||||
start the dual servers (dapp + mascara) |
||||
``` |
||||
node server.js |
||||
npm run mascara |
||||
``` |
||||
|
||||
## First time use: |
||||
### First time use: |
||||
|
||||
- navigate to: http://localhost:9001/popup/popup.html |
||||
- navigate to: http://localhost:9001 |
||||
- Create an Account |
||||
- go back to http://localhost:9002/ |
||||
- go back to http://localhost:9002 |
||||
- open devTools |
||||
- click Sync Tx |
||||
|
||||
### Todos |
||||
### Tests: |
||||
|
||||
``` |
||||
npm run testMascara |
||||
``` |
||||
|
||||
Test will run in browser, you will have to have these browsers installed: |
||||
|
||||
- Chrome |
||||
- Firefox |
||||
- Opera |
||||
|
||||
- [ ] Figure out user flows and UI redesign |
||||
- [ ] Figure out FireFox |
||||
Standing problems: |
||||
- [ ] IndexDb |
||||
|
||||
### Deploy: |
||||
|
||||
Will build and deploy mascara via docker |
||||
|
||||
``` |
||||
docker-compose build && docker-compose stop && docker-compose up -d && docker-compose logs --tail 200 -f |
||||
``` |
@ -1,88 +0,0 @@ |
||||
const EventEmitter = require('events') |
||||
module.exports = class IndexDbController extends EventEmitter { |
||||
|
||||
constructor (opts) { |
||||
super() |
||||
this.migrations = opts.migrations |
||||
this.key = opts.key |
||||
this.dbObject = global.indexedDB |
||||
this.IDBTransaction = global.IDBTransaction || global.webkitIDBTransaction || global.msIDBTransaction || {READ_WRITE: "readwrite"}; // This line should only be needed if it is needed to support the object's constants for older browsers
|
||||
this.IDBKeyRange = global.IDBKeyRange || global.webkitIDBKeyRange || global.msIDBKeyRange; |
||||
this.version = opts.version |
||||
this.logging = opts.logging |
||||
this.initialState = opts.initialState |
||||
if (this.logging) this.on('log', logger) |
||||
} |
||||
|
||||
// Opens the database connection and returns a promise
|
||||
open (version = this.version) { |
||||
return new Promise((resolve, reject) => { |
||||
const dbOpenRequest = this.dbObject.open(this.key, version) |
||||
dbOpenRequest.onerror = (event) => { |
||||
return reject(event) |
||||
} |
||||
dbOpenRequest.onsuccess = (event) => { |
||||
this.db = dbOpenRequest.result |
||||
this.emit('success') |
||||
resolve(this.db) |
||||
} |
||||
dbOpenRequest.onupgradeneeded = (event) => { |
||||
this.db = event.target.result |
||||
this.db.createObjectStore('dataStore') |
||||
} |
||||
}) |
||||
.then((openRequest) => { |
||||
return this.get('dataStore') |
||||
}) |
||||
.then((data) => { |
||||
if (!data) { |
||||
return this._add('dataStore', this.initialState) |
||||
.then(() => this.get('dataStore')) |
||||
.then((versionedData) => Promise.resolve(versionedData)) |
||||
} |
||||
return Promise.resolve(data) |
||||
}) |
||||
} |
||||
|
||||
requestObjectStore (key, type = 'readonly') { |
||||
return new Promise((resolve, reject) => { |
||||
const dbReadWrite = this.db.transaction(key, type) |
||||
const dataStore = dbReadWrite.objectStore(key) |
||||
resolve(dataStore) |
||||
}) |
||||
} |
||||
|
||||
get (key = 'dataStore') { |
||||
return this.requestObjectStore(key) |
||||
.then((dataObject)=> { |
||||
return new Promise((resolve, reject) => { |
||||
const getRequest = dataObject.get(key) |
||||
getRequest.onsuccess = (event) => resolve(event.currentTarget.result) |
||||
getRequest.onerror = (event) => reject(event) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
put (state) { |
||||
return this.requestObjectStore('dataStore', 'readwrite') |
||||
.then((dataObject)=> { |
||||
const putRequest = dataObject.put(state, 'dataStore') |
||||
putRequest.onsuccess = (event) => Promise.resolve(event.currentTarget.result) |
||||
putRequest.onerror = (event) => Promise.reject(event) |
||||
}) |
||||
} |
||||
|
||||
_add (key, objStore, cb = logger) { |
||||
return this.requestObjectStore(key, 'readwrite') |
||||
.then((dataObject)=> { |
||||
const addRequest = dataObject.add(objStore, key) |
||||
addRequest.onsuccess = (event) => Promise.resolve(event.currentTarget.result) |
||||
addRequest.onerror = (event) => Promise.reject(event) |
||||
}) |
||||
} |
||||
|
||||
} |
||||
|
||||
function logger (err, ress) { |
||||
err ? console.error(`Logger says: ${err}`) : console.dir(`Logger says: ${ress}`) |
||||
} |
@ -0,0 +1,7 @@ |
||||
function wait(time) { |
||||
return new Promise(function(resolve, reject) { |
||||
setTimeout(function() { |
||||
resolve() |
||||
}, time * 3 || 1500) |
||||
}) |
||||
} |
@ -0,0 +1,21 @@ |
||||
<!DOCTYPE html> |
||||
<html> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<meta name="viewport" content="width=device-width"> |
||||
<title>QUnit Example</title> |
||||
<link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.0.0.css"> |
||||
</head> |
||||
<body> |
||||
<div id="qunit"></div> |
||||
<div id="qunit-fixture"></div> |
||||
<script src="https://code.jquery.com/qunit/qunit-2.0.0.js"></script> |
||||
<script src="./jquery-3.1.0.min.js"></script> |
||||
<script src="./helpers.js"></script> |
||||
<script src="./test-bundle.js"></script> |
||||
<script src="/testem.js"></script> |
||||
|
||||
<div id="app-content"></div> |
||||
<script src="./bundle.js"></script> |
||||
</body> |
||||
</html> |
@ -0,0 +1,22 @@ |
||||
var fs = require('fs') |
||||
var path = require('path') |
||||
var browserify = require('browserify'); |
||||
var tests = fs.readdirSync(path.join(__dirname, 'lib')) |
||||
var bundlePath = path.join(__dirname, 'test-bundle.js') |
||||
var b = browserify(); |
||||
|
||||
// Remove old bundle
|
||||
try { |
||||
fs.unlinkSync(bundlePath) |
||||
} catch (e) { |
||||
console.error(e) |
||||
} |
||||
|
||||
var writeStream = fs.createWriteStream(bundlePath) |
||||
|
||||
tests.forEach(function(fileName) { |
||||
b.add(path.join(__dirname, 'lib', fileName)) |
||||
}) |
||||
|
||||
b.bundle().pipe(writeStream); |
||||
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,119 @@ |
||||
const PASSWORD = 'password123' |
||||
|
||||
QUnit.module('first time usage') |
||||
|
||||
QUnit.test('render init screen', function (assert) { |
||||
var done = assert.async() |
||||
let app |
||||
|
||||
wait(1000).then(function() { |
||||
app = $('#app-content').contents() |
||||
const recurseNotices = function () { |
||||
let button = app.find('button') |
||||
if (button.html() === 'Accept') { |
||||
let termsPage = app.find('.markdown')[0] |
||||
termsPage.scrollTop = termsPage.scrollHeight |
||||
return wait().then(() => { |
||||
button.click() |
||||
return wait() |
||||
}).then(() => { |
||||
return recurseNotices() |
||||
}) |
||||
} else { |
||||
return wait() |
||||
} |
||||
} |
||||
return recurseNotices() |
||||
}).then(function() { |
||||
// Scroll through terms
|
||||
var title = app.find('h1').text() |
||||
assert.equal(title, 'MetaMask', 'title screen') |
||||
|
||||
// enter password
|
||||
var pwBox = app.find('#password-box')[0] |
||||
var confBox = app.find('#password-box-confirm')[0] |
||||
pwBox.value = PASSWORD |
||||
confBox.value = PASSWORD |
||||
|
||||
return wait() |
||||
}).then(function() { |
||||
|
||||
// create vault
|
||||
var createButton = app.find('button.primary')[0] |
||||
createButton.click() |
||||
|
||||
return wait(1500) |
||||
}).then(function() { |
||||
|
||||
var created = app.find('h3')[0] |
||||
assert.equal(created.textContent, 'Vault Created', 'Vault created screen') |
||||
|
||||
// Agree button
|
||||
var button = app.find('button')[0] |
||||
assert.ok(button, 'button present') |
||||
button.click() |
||||
|
||||
return wait(1000) |
||||
}).then(function() { |
||||
|
||||
var detail = app.find('.account-detail-section')[0] |
||||
assert.ok(detail, 'Account detail section loaded.') |
||||
|
||||
var sandwich = app.find('.sandwich-expando')[0] |
||||
sandwich.click() |
||||
|
||||
return wait() |
||||
}).then(function() { |
||||
|
||||
var sandwich = app.find('.menu-droppo')[0] |
||||
var children = sandwich.children |
||||
var lock = children[children.length - 2] |
||||
assert.ok(lock, 'Lock menu item found') |
||||
lock.click() |
||||
|
||||
return wait(1000) |
||||
}).then(function() { |
||||
|
||||
var pwBox = app.find('#password-box')[0] |
||||
pwBox.value = PASSWORD |
||||
|
||||
var createButton = app.find('button.primary')[0] |
||||
createButton.click() |
||||
|
||||
return wait(1000) |
||||
}).then(function() { |
||||
|
||||
var detail = app.find('.account-detail-section')[0] |
||||
assert.ok(detail, 'Account detail section loaded again.') |
||||
|
||||
return wait() |
||||
}).then(function (){ |
||||
|
||||
var qrButton = app.find('.fa.fa-qrcode')[0] |
||||
qrButton.click() |
||||
|
||||
return wait(1000) |
||||
}).then(function (){ |
||||
|
||||
var qrHeader = app.find('.qr-header')[0] |
||||
var qrContainer = app.find('#qr-container')[0] |
||||
assert.equal(qrHeader.textContent, 'Account 1', 'Should show account label.') |
||||
assert.ok(qrContainer, 'QR Container found') |
||||
|
||||
return wait() |
||||
}).then(function (){ |
||||
|
||||
var networkMenu = app.find('.network-indicator')[0] |
||||
networkMenu.click() |
||||
|
||||
return wait() |
||||
}).then(function (){ |
||||
|
||||
var networkMenu = app.find('.network-indicator')[0] |
||||
var children = networkMenu.children |
||||
children.length[3] |
||||
assert.ok(children, 'All network options present') |
||||
|
||||
done() |
||||
}) |
||||
}) |
@ -0,0 +1,13 @@ |
||||
launch_in_dev: |
||||
- Chrome |
||||
- Firefox |
||||
- Opera |
||||
launch_in_ci: |
||||
- Chrome |
||||
- Firefox |
||||
- Opera |
||||
framework: |
||||
- qunit |
||||
before_tests: "npm run mascaraCi" |
||||
after_tests: "rm ./background.js ./test-bundle.js ./bundle.js" |
||||
test_page: "./index.html" |
@ -0,0 +1,40 @@ |
||||
const EventEmitter = require('events') |
||||
const IDB = require('idb-global') |
||||
const KEY = 'metamask-test-config' |
||||
module.exports = class Helper extends EventEmitter { |
||||
constructor () { |
||||
super() |
||||
} |
||||
|
||||
tryToCleanContext () { |
||||
this.unregister() |
||||
.then(() => this.clearDb()) |
||||
.then(() => super.emit('complete')) |
||||
.catch((err) => super.emit('complete')) |
||||
} |
||||
|
||||
unregister () { |
||||
return global.navigator.serviceWorker.getRegistration() |
||||
.then((registration) => { |
||||
if (registration) return registration.unregister() |
||||
.then((b) => b ? Promise.resolve() : Promise.reject()) |
||||
else return Promise.resolve() |
||||
}) |
||||
} |
||||
clearDb () { |
||||
return new Promise ((resolve, reject) => { |
||||
const deleteRequest = global.indexDB.deleteDatabase(KEY) |
||||
deleteRequest.addEventListener('success', resolve) |
||||
deleteRequest.addEventListener('error', reject) |
||||
}) |
||||
|
||||
} |
||||
mockState (state) { |
||||
const db = new IDB({ |
||||
version: 2, |
||||
key: KEY, |
||||
initialState: state |
||||
}) |
||||
return db.open() |
||||
} |
||||
} |
@ -0,0 +1,5 @@ |
||||
const Helper = require('./util/mascara-test-helper.js') |
||||
|
||||
window.addEventListener('load', () => { |
||||
require('../src/ui.js') |
||||
}) |
@ -1 +0,0 @@ |
||||
MetaMask now lists a new network on our dropdown list: Kovan, a [Proof of Authority](https://github.com/paritytech/parity/wiki/Proof-of-Authority-Chains) testchain managed by several blockchain organizations such as Digix, Etherscan, and Parity. It is designed to be a more stable and reliable testnet alternative to Ropsten and was created in response to recent attacks that slowed down the Ropsten network. You can read more about Kovan [here](https://medium.com/@Digix/announcing-kovan-a-stable-ethereum-public-testnet-10ac7cb6c85f#.6o8sz8cct) and [here](https://medium.com/@Digix/letter-from-the-ceo-some-context-regarding-kovan-7b5121adb901#.kfv7zhw83). As with Ropsten, the default remote node to connect to Kovan is managed by Infura. |
@ -0,0 +1,8 @@ |
||||
MetaMask is beta software. |
||||
|
||||
When you log in to MetaMask, your current account is visible to every new site you visit. |
||||
|
||||
For your privacy, for now, please sign out of MetaMask when you're done using a site. |
||||
|
||||
Also, by default, you will be signed in to a test network. To use real Ether, you must connect to the main network manually in the top left network menu. |
||||
|
@ -1 +1 @@ |
||||
2 |
||||
3 |
File diff suppressed because one or more lines are too long
@ -1 +1,14 @@ |
||||
{"version":0,"data":{"wallet":"{\"encSeed\":{\"encStr\":\"rT1C1jjkFRfmrwefscFcwZohl4f+HfIFlBZ9AM4ZD8atJmfKDIQCVK11NYDKYv8ZMIY03f3t8MuoZvfzBL8IJsWnZUhpzVTNNiARQJD2WpGA19eNBzgZm4vd0GwkIUruUDeJXu0iv2j9wU8hOQUqPbOePPy2Am5ro97iuvMAroRTnEKD60qFVg==\",\"nonce\":\"YUY2mwNq2v3FV0Fi94QnSiKFOLYfDR95\"},\"ksData\":{\"m/44'/60'/0'/0\":{\"info\":{\"curve\":\"secp256k1\",\"purpose\":\"sign\"},\"encHdPathPriv\":{\"encStr\":\"Iyi7ft4JQ9UtwrSXRT6ZIHPtZqJhe99rh0uWhNc6QLan6GanY2ZQeU0tt76CBealEWJyrJReSxGQdqDmSDYjpjH3m4JO5l0DfPLPseCqzXV/W+dzM0ubJ8lztLwpwi0L+vULNMqCx4dQtoNbNBq1QZUnjtpm6O8mWpScspboww==\",\"nonce\":\"Z7RqtjNjC6FrLUj5wVW1+HkjOW6Hib6K\"},\"hdIndex\":3,\"encPrivKeys\":{\"edb81c10122f34040cc4bef719a272fbbb1cf897\":{\"key\":\"8ab81tKBd4+CLAbzvS7SBFRTd6VWXBs86uBE43lgcmBu2U7UB22xdH64Q2hUf9eB\",\"nonce\":\"aGUEqI033FY39zKjWmZSI6PQrCLvkiRP\"},\"8bd7d5c000cf05284e98356370dc5ccaa3dbfc38\":{\"key\":\"+i3wmf4b+B898QtlOBfL0Ixirjg59/LLPX61vQ2L0xRPjXzNog0O4Wn15RemM5mY\",\"nonce\":\"imKrlkuoC5uuFkzJBbuDBluGCPJXNTKm\"},\"2340695474656e3124b8eba1172fbfb00eeac8f8\":{\"key\":\"pi+H9D8LYKsdCQKrfaJtsGFjE+X9s74xN675tsoIKrbPXhtpxMLOIQVtSqYveF62\",\"nonce\":\"49g80wDTovHwbguVVYf2FsYbp7Db5OAR\"}},\"addresses\":[\"edb81c10122f34040cc4bef719a272fbbb1cf897\",\"8bd7d5c000cf05284e98356370dc5ccaa3dbfc38\",\"2340695474656e3124b8eba1172fbfb00eeac8f8\"]}},\"version\":2}","config":{"provider":{"type":"etherscan"}}},"meta":{"version":0}} |
||||
{ |
||||
"version": 0, |
||||
"data": { |
||||
"wallet": "{\"encSeed\":{\"encStr\":\"rT1C1jjkFRfmrwefscFcwZohl4f+HfIFlBZ9AM4ZD8atJmfKDIQCVK11NYDKYv8ZMIY03f3t8MuoZvfzBL8IJsWnZUhpzVTNNiARQJD2WpGA19eNBzgZm4vd0GwkIUruUDeJXu0iv2j9wU8hOQUqPbOePPy2Am5ro97iuvMAroRTnEKD60qFVg==\",\"nonce\":\"YUY2mwNq2v3FV0Fi94QnSiKFOLYfDR95\"},\"ksData\":{\"m/44'/60'/0'/0\":{\"info\":{\"curve\":\"secp256k1\",\"purpose\":\"sign\"},\"encHdPathPriv\":{\"encStr\":\"Iyi7ft4JQ9UtwrSXRT6ZIHPtZqJhe99rh0uWhNc6QLan6GanY2ZQeU0tt76CBealEWJyrJReSxGQdqDmSDYjpjH3m4JO5l0DfPLPseCqzXV/W+dzM0ubJ8lztLwpwi0L+vULNMqCx4dQtoNbNBq1QZUnjtpm6O8mWpScspboww==\",\"nonce\":\"Z7RqtjNjC6FrLUj5wVW1+HkjOW6Hib6K\"},\"hdIndex\":3,\"encPrivKeys\":{\"edb81c10122f34040cc4bef719a272fbbb1cf897\":{\"key\":\"8ab81tKBd4+CLAbzvS7SBFRTd6VWXBs86uBE43lgcmBu2U7UB22xdH64Q2hUf9eB\",\"nonce\":\"aGUEqI033FY39zKjWmZSI6PQrCLvkiRP\"},\"8bd7d5c000cf05284e98356370dc5ccaa3dbfc38\":{\"key\":\"+i3wmf4b+B898QtlOBfL0Ixirjg59/LLPX61vQ2L0xRPjXzNog0O4Wn15RemM5mY\",\"nonce\":\"imKrlkuoC5uuFkzJBbuDBluGCPJXNTKm\"},\"2340695474656e3124b8eba1172fbfb00eeac8f8\":{\"key\":\"pi+H9D8LYKsdCQKrfaJtsGFjE+X9s74xN675tsoIKrbPXhtpxMLOIQVtSqYveF62\",\"nonce\":\"49g80wDTovHwbguVVYf2FsYbp7Db5OAR\"}},\"addresses\":[\"edb81c10122f34040cc4bef719a272fbbb1cf897\",\"8bd7d5c000cf05284e98356370dc5ccaa3dbfc38\",\"2340695474656e3124b8eba1172fbfb00eeac8f8\"]}},\"version\":2}", |
||||
"config": { |
||||
"provider": { |
||||
"type": "etherscan" |
||||
} |
||||
} |
||||
}, |
||||
"meta": { |
||||
"version": 0 |
||||
} |
||||
} |
@ -0,0 +1,18 @@ |
||||
const createStore = require('redux').createStore |
||||
const applyMiddleware = require('redux').applyMiddleware |
||||
const thunkMiddleware = require('redux-thunk') |
||||
const createLogger = require('redux-logger') |
||||
const rootReducer = function () {} |
||||
|
||||
module.exports = configureStore |
||||
|
||||
const loggerMiddleware = createLogger() |
||||
|
||||
const createStoreWithMiddleware = applyMiddleware( |
||||
thunkMiddleware, |
||||
loggerMiddleware |
||||
)(createStore) |
||||
|
||||
function configureStore (initialState) { |
||||
return createStoreWithMiddleware(rootReducer, initialState) |
||||
} |
@ -0,0 +1,51 @@ |
||||
var assert = require('assert') |
||||
|
||||
const additions = require('react-testutils-additions') |
||||
const h = require('react-hyperscript') |
||||
const ReactTestUtils = require('react-addons-test-utils') |
||||
const ethUtil = require('ethereumjs-util') |
||||
const BN = ethUtil.BN |
||||
|
||||
var BnInput = require('../../../ui/app/components/bn-as-decimal-input') |
||||
|
||||
describe('BnInput', function () { |
||||
it('can tolerate a gas decimal number at a high precision', function (done) { |
||||
const renderer = ReactTestUtils.createRenderer() |
||||
|
||||
let valueStr = '20' |
||||
while (valueStr.length < 20) { |
||||
valueStr += '0' |
||||
} |
||||
const value = new BN(valueStr, 10) |
||||
|
||||
const inputStr = '2.3' |
||||
|
||||
let targetStr = '23' |
||||
while (targetStr.length < 19) { |
||||
targetStr += '0' |
||||
} |
||||
const target = new BN(targetStr, 10) |
||||
|
||||
const precision = 18 // ether precision
|
||||
const scale = 18 |
||||
|
||||
const props = { |
||||
value, |
||||
scale, |
||||
precision, |
||||
onChange: (newBn) => { |
||||
assert.equal(newBn.toString(), target.toString(), 'should tolerate increase') |
||||
done() |
||||
}, |
||||
} |
||||
|
||||
const inputComponent = h(BnInput, props) |
||||
const component = additions.renderIntoDocument(inputComponent) |
||||
renderer.render(inputComponent) |
||||
const input = additions.find(component, 'input.hex-input')[0] |
||||
ReactTestUtils.Simulate.change(input, { preventDefault () {}, target: { |
||||
value: inputStr, |
||||
checkValidity () { return true } }, |
||||
}) |
||||
}) |
||||
}) |
@ -0,0 +1,77 @@ |
||||
const assert = require('assert') |
||||
const additions = require('react-testutils-additions') |
||||
const h = require('react-hyperscript') |
||||
const PendingTx = require('../../../ui/app/components/pending-tx') |
||||
const ReactTestUtils = require('react-addons-test-utils') |
||||
const ethUtil = require('ethereumjs-util') |
||||
|
||||
describe('PendingTx', function () { |
||||
const identities = { |
||||
'0xfdea65c8e26263f6d9a1b5de9555d2931a33b826': { |
||||
name: 'Main Account 1', |
||||
balance: '0x00000000000000056bc75e2d63100000', |
||||
}, |
||||
} |
||||
|
||||
const gasPrice = '0x4A817C800' // 20 Gwei
|
||||
const txData = { |
||||
'id': 5021615666270214, |
||||
'time': 1494458763011, |
||||
'status': 'unapproved', |
||||
'metamaskNetworkId': '1494442339676', |
||||
'txParams': { |
||||
'from': '0xfdea65c8e26263f6d9a1b5de9555d2931a33b826', |
||||
'to': '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb', |
||||
'value': '0xde0b6b3a7640000', |
||||
gasPrice, |
||||
'gas': '0x7b0c'}, |
||||
'gasLimitSpecified': false, |
||||
'estimatedGas': '0x5208', |
||||
} |
||||
|
||||
|
||||
it('should use updated values when edited.', function (done) { |
||||
const renderer = ReactTestUtils.createRenderer() |
||||
const newGasPrice = '0x77359400' |
||||
|
||||
const props = { |
||||
identities, |
||||
accounts: identities, |
||||
txData, |
||||
sendTransaction: (txMeta, event) => { |
||||
// Assert changes:
|
||||
const result = ethUtil.addHexPrefix(txMeta.txParams.gasPrice) |
||||
assert.notEqual(result, gasPrice, 'gas price should change') |
||||
assert.equal(result, newGasPrice, 'gas price assigned.') |
||||
done() |
||||
}, |
||||
} |
||||
|
||||
const pendingTxComponent = h(PendingTx, props) |
||||
const component = additions.renderIntoDocument(pendingTxComponent) |
||||
renderer.render(pendingTxComponent) |
||||
const result = renderer.getRenderOutput() |
||||
assert.equal(result.type, 'div', 'should create a div') |
||||
|
||||
try { |
||||
const input = additions.find(component, '.cell.row input[type="number"]')[1] |
||||
ReactTestUtils.Simulate.change(input, { |
||||
target: { |
||||
value: 2, |
||||
checkValidity () { return true }, |
||||
}, |
||||
}) |
||||
|
||||
const form = additions.find(component, 'form')[0] |
||||
form.checkValidity = () => true |
||||
form.getFormEl = () => { return { checkValidity () { return true } } } |
||||
ReactTestUtils.Simulate.submit(form, { preventDefault () {}, target: { checkValidity () { |
||||
return true |
||||
} } }) |
||||
} catch (e) { |
||||
console.log('WHAAAA') |
||||
console.error(e) |
||||
} |
||||
}) |
||||
}) |
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue