From 8b62a8bec288120eee71523886f4c2df83b136ff Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 18 Apr 2016 11:31:06 -0700 Subject: Fix plugin tests --- package.json | 3 ++- test/helper.js | 3 +++ test/unit/migrations-test.js | 13 ++++++++----- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index db0e2823a..f6bde2e4e 100644 --- a/package.json +++ b/package.json @@ -41,13 +41,14 @@ "metamask-logo": "^1.1.5", "multiplex": "^6.7.0", "pojo-migrator": "^2.1.0", + "polyfill-crypto.getrandomvalues": "^1.0.0", "pumpify": "^1.3.4", "react": "^0.14.3", "react-addons-css-transition-group": "^0.14.7", "react-dom": "^0.14.3", "react-hyperscript": "^2.2.2", - "readable-stream": "^2.0.5", "react-redux": "^4.0.3", + "readable-stream": "^2.0.5", "redux": "^3.0.5", "redux-logger": "^2.3.1", "redux-thunk": "^1.0.2", diff --git a/test/helper.js b/test/helper.js index 4c7f8b4c6..64fe5bd07 100644 --- a/test/helper.js +++ b/test/helper.js @@ -1,2 +1,5 @@ require('jsdom-global')() window.localStorage = {} + +if (!('crypto' in window)) { window.crypto = {} } +window.crypto.getRandomValues = require('polyfill-crypto.getrandomvalues') diff --git a/test/unit/migrations-test.js b/test/unit/migrations-test.js index 3a3213ac5..3429ffb1d 100644 --- a/test/unit/migrations-test.js +++ b/test/unit/migrations-test.js @@ -1,14 +1,17 @@ +var assert = require('assert') var test = require('tape') var path = require('path') var wallet1 = require(path.join('..', 'lib', 'migrations', '001.json')) var migration2 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '002')) -test('wallet1 is migrated successfully', function(t) { - - var result = migration2.migrate(wallet1.data) - t.equal(result.config.provider.type, 'rpc', 'provider should be rpc') - t.equal(result.config.provider.rpcTarget, 'https://rpc.metamask.io/', 'provider should be our rpc') +describe('wallet1 is migrated successfully', function() { + it('should convert etherscan provider', function(done) { + var result = migration2.migrate(wallet1.data) + assert.equal(result.config.provider.type, 'rpc', 'provider should be rpc') + assert.equal(result.config.provider.rpcTarget, 'https://rpc.metamask.io/', 'provider should be our rpc') + done() + }) }) -- cgit From 65d73d7bb4b091021988b6115d518cf3914952ed Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 18 Apr 2016 11:41:29 -0700 Subject: Unify test suites --- README.md | 13 +- package.json | 7 +- test/unit/actions/config_test.js | 43 ++++++ test/unit/actions/restore_vault_test.js | 54 +++++++ test/unit/actions/set_selected_account_test.js | 28 ++++ test/unit/actions/tx_test.js | 168 ++++++++++++++++++++++ test/unit/actions/view_info_test.js | 23 +++ test/unit/actions/warning_test.js | 24 ++++ test/unit/migrations-test.js | 1 - test/unit/util_test.js | 114 +++++++++++++++ ui/test/setup.js | 8 -- ui/test/unit/actions/config_test.js | 43 ------ ui/test/unit/actions/restore_vault_test.js | 54 ------- ui/test/unit/actions/set_selected_account_test.js | 28 ---- ui/test/unit/actions/tx_test.js | 168 ---------------------- ui/test/unit/actions/view_info_test.js | 23 --- ui/test/unit/actions/warning_test.js | 24 ---- ui/test/unit/util_test.js | 114 --------------- 18 files changed, 459 insertions(+), 478 deletions(-) create mode 100644 test/unit/actions/config_test.js create mode 100644 test/unit/actions/restore_vault_test.js create mode 100644 test/unit/actions/set_selected_account_test.js create mode 100644 test/unit/actions/tx_test.js create mode 100644 test/unit/actions/view_info_test.js create mode 100644 test/unit/actions/warning_test.js create mode 100644 test/unit/util_test.js delete mode 100644 ui/test/setup.js delete mode 100644 ui/test/unit/actions/config_test.js delete mode 100644 ui/test/unit/actions/restore_vault_test.js delete mode 100644 ui/test/unit/actions/set_selected_account_test.js delete mode 100644 ui/test/unit/actions/tx_test.js delete mode 100644 ui/test/unit/actions/view_info_test.js delete mode 100644 ui/test/unit/actions/warning_test.js delete mode 100644 ui/test/unit/util_test.js diff --git a/README.md b/README.md index 1d15fa204..1c5508a49 100644 --- a/README.md +++ b/README.md @@ -44,18 +44,11 @@ To enjoy the live-reloading that `gulp dev` offers while working on the `web3-pr ### Running Tests -Currently the tests are split between two suites (we recently merged the UI into the main plugin repository). There are two different test suites to be concerned with: - -Plugin tests, `npm test`. -UI tests, `npm run testUi`. - -You can also run both of these with continuously watching processes, via `npm run watch` and `npm run watchUi`. - -#### UI Testing Particulars - Requires `mocha` installed. Run `npm install -g mocha`. -You can either run the test suite once with `npm testUi`, or you can reload on file changes, by running `mocha watch ui/test/**/**`. +Then just run `npm test`. + +You can also test with a continuously watching process, via `npm run watch`. ### Deploying the UI diff --git a/package.json b/package.json index f6bde2e4e..3981f3058 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,8 @@ "private": true, "scripts": { "start": "gulp dev", - "test": "npm run testUi", - "testPlugin": "mocha --require test/helper.js --compilers js:babel-register --recursive", - "watch": "mocha watch --compilers js:babel-register --recursive", - "testUi": "mocha ui/test/**/**/*test.js", - "watchUi": "mocha watch ui/test/**/*test.js" + "test": "mocha --require test/helper.js --compilers js:babel-register --recursive", + "watch": "mocha watch --compilers js:babel-register --recursive" }, "browserify": { "transform": [ diff --git a/test/unit/actions/config_test.js b/test/unit/actions/config_test.js new file mode 100644 index 000000000..6a0d20f31 --- /dev/null +++ b/test/unit/actions/config_test.js @@ -0,0 +1,43 @@ +var jsdom = require('mocha-jsdom') +var assert = require('assert') +var freeze = require('deep-freeze-strict') +var path = require('path') + +var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) + +describe ('config view actions', function() { + + var initialState = { + metamask: { + rpcTarget: 'foo', + }, + appState: { + currentView: { + name: 'accounts', + } + } + } + freeze(initialState) + + describe('SHOW_CONFIG_PAGE', function() { + it('should set appState.currentView.name to config', function() { + var result = reducers(initialState, actions.showConfigPage()) + assert.equal(result.appState.currentView.name, 'config') + }) + }) + + describe('SET_RPC_TARGET', function() { + + it('sets the state.metamask.rpcTarget property of the state to the action.value', function() { + const action = { + type: actions.SET_RPC_TARGET, + value: 'bar', + } + + var result = reducers(initialState, action) + assert.equal(result.metamask.rpcTarget, action.value) + }) + }) +}) + diff --git a/test/unit/actions/restore_vault_test.js b/test/unit/actions/restore_vault_test.js new file mode 100644 index 000000000..5873a0181 --- /dev/null +++ b/test/unit/actions/restore_vault_test.js @@ -0,0 +1,54 @@ +var jsdom = require('mocha-jsdom') +var assert = require('assert') +var freeze = require('deep-freeze-strict') +var path = require('path') +var sinon = require('sinon') + +var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) + +describe('#recoverFromSeed(password, seed)', function() { + + beforeEach(function() { + // sinon allows stubbing methods that are easily verified + this.sinon = sinon.sandbox.create() + }) + + afterEach(function() { + // sinon requires cleanup otherwise it will overwrite context + this.sinon.restore() + }) + + // stub out account manager + actions._setAccountManager({ + recoverFromSeed(pw, seed, cb) { cb() }, + }) + + it('sets metamask.isUnlocked to true', function() { + var initialState = { + metamask: { + isUnlocked: false, + isInitialized: false, + } + } + freeze(initialState) + + const restorePhrase = 'invite heavy among daring outdoor dice jelly coil stable note seat vicious' + const password = 'foo' + const dispatchFunc = actions.recoverFromSeed(password, restorePhrase) + + var dispatchStub = this.sinon.stub() + dispatchStub.withArgs({ TYPE: actions.unlockMetamask() }).onCall(0) + dispatchStub.withArgs({ TYPE: actions.showAccountsPage() }).onCall(1) + + var action + var resultingState = initialState + dispatchFunc((newAction) => { + action = newAction + resultingState = reducers(resultingState, action) + }) + + assert.equal(resultingState.metamask.isUnlocked, true, 'was unlocked') + assert.equal(resultingState.metamask.isInitialized, true, 'was initialized') + }); +}); diff --git a/test/unit/actions/set_selected_account_test.js b/test/unit/actions/set_selected_account_test.js new file mode 100644 index 000000000..0487bc5f0 --- /dev/null +++ b/test/unit/actions/set_selected_account_test.js @@ -0,0 +1,28 @@ +var jsdom = require('mocha-jsdom') +var assert = require('assert') +var freeze = require('deep-freeze-strict') +var path = require('path') + +var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) + +describe('SET_SELECTED_ACCOUNT', function() { + + it('sets the state.appState.activeAddress property of the state to the action.value', function() { + var initialState = { + appState: { + activeAddress: 'foo', + } + } + freeze(initialState) + + const action = { + type: actions.SET_SELECTED_ACCOUNT, + value: 'bar', + } + freeze(action) + + var resultingState = reducers(initialState, action) + assert.equal(resultingState.appState.activeAddress, action.value) + }); +}); diff --git a/test/unit/actions/tx_test.js b/test/unit/actions/tx_test.js new file mode 100644 index 000000000..b15bee393 --- /dev/null +++ b/test/unit/actions/tx_test.js @@ -0,0 +1,168 @@ +var jsdom = require('mocha-jsdom') +var assert = require('assert') +var freeze = require('deep-freeze-strict') +var path = require('path') + +var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) + +describe('tx confirmation screen', function() { + var initialState, result + + describe('when there is only one tx', function() { + var firstTxId = 1457634084250832 + + beforeEach(function() { + + initialState = { + appState: { + currentView: { + name: 'confTx', + }, + }, + metamask: { + unconfTxs: { + '1457634084250832': { + id: 1457634084250832, + status: "unconfirmed", + time: 1457634084250, + } + }, + } + } + freeze(initialState) + }) + + describe('cancelTx', function() { + + before(function(done) { + actions._setAccountManager({ + approveTransaction(txId, cb) { cb('An error!') }, + cancelTransaction(txId) { /* noop */ }, + clearSeedWordCache(cb) { cb() }, + }) + + actions.cancelTx({id: firstTxId})(function(action) { + result = reducers(initialState, action) + done() + }) + }) + + it('should transition to the accounts list', function() { + assert.equal(result.appState.currentView.name, 'accounts') + }) + + it('should have no unconfirmed txs remaining', function() { + var count = getUnconfirmedTxCount(result) + assert.equal(count, 0) + }) + }) + + describe('sendTx', function() { + var result + + describe('when there is an error', function() { + + before(function(done) { + alert = () => {/* noop */} + + actions._setAccountManager({ + approveTransaction(txId, cb) { cb('An error!') }, + }) + + actions.sendTx({id: firstTxId})(function(action) { + result = reducers(initialState, action) + done() + }) + }) + + it('should stay on the page', function() { + assert.equal(result.appState.currentView.name, 'confTx') + }) + + it('should set errorMessage on the currentView', function() { + assert(result.appState.currentView.errorMessage) + }) + }) + + describe('when there is success', function() { + before(function(done) { + actions._setAccountManager({ + approveTransaction(txId, cb) { cb() }, + }) + + actions.sendTx({id: firstTxId})(function(action) { + result = reducers(initialState, action) + done() + }) + }) + + it('should navigate away from the tx page', function() { + assert.equal(result.appState.currentView.name, 'accounts') + }) + + it('should clear the tx from the unconfirmed transactions', function() { + assert(!(firstTxId in result.metamask.unconfTxs), 'tx is cleared') + }) + }) + }) + + describe('when there are two pending txs', function() { + var firstTxId = 1457634084250832 + var result, initialState + before(function(done) { + initialState = { + appState: { + currentView: { + name: 'confTx', + }, + }, + metamask: { + unconfTxs: { + '1457634084250832': { + id: 1457634084250832, + status: "unconfirmed", + time: 1457634084250, + }, + '1457634084250833': { + id: 1457634084250833, + status: "unconfirmed", + time: 1457634084255, + }, + }, + } + } + freeze(initialState) + + + actions._setAccountManager({ + approveTransaction(txId, cb) { cb() }, + }) + + actions.sendTx({id: firstTxId})(function(action) { + result = reducers(initialState, action) + done() + }) + }) + + it('should stay on the confTx view', function() { + assert.equal(result.appState.currentView.name, 'confTx') + }) + + it('should transition to the first tx', function() { + assert.equal(result.appState.currentView.context, 0) + }) + + it('should only have one unconfirmed tx remaining', function() { + var count = getUnconfirmedTxCount(result) + assert.equal(count, 1) + }) + }) + }) +}); + +function getUnconfirmedTxCount(state) { + var txs = state.metamask.unconfTxs + var count = Object.keys(txs).length + return count +} diff --git a/test/unit/actions/view_info_test.js b/test/unit/actions/view_info_test.js new file mode 100644 index 000000000..0558c6e42 --- /dev/null +++ b/test/unit/actions/view_info_test.js @@ -0,0 +1,23 @@ +var jsdom = require('mocha-jsdom') +var assert = require('assert') +var freeze = require('deep-freeze-strict') +var path = require('path') + +var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) + +describe('SHOW_INFO_PAGE', function() { + + it('sets the state.appState.currentView.name property to info', function() { + var initialState = { + appState: { + activeAddress: 'foo', + } + } + freeze(initialState) + + const action = actions.showInfoPage() + var resultingState = reducers(initialState, action) + assert.equal(resultingState.appState.currentView.name, 'info') + }); +}); diff --git a/test/unit/actions/warning_test.js b/test/unit/actions/warning_test.js new file mode 100644 index 000000000..37be9ee85 --- /dev/null +++ b/test/unit/actions/warning_test.js @@ -0,0 +1,24 @@ +var jsdom = require('mocha-jsdom') +var assert = require('assert') +var freeze = require('deep-freeze-strict') +var path = require('path') + +var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) + +describe('action DISPLAY_WARNING', function() { + + it('sets appState.warning to provided value', function() { + var initialState = { + appState: {}, + } + freeze(initialState) + + const warningText = 'This is a sample warning message' + + const action = actions.displayWarning(warningText) + const resultingState = reducers(initialState, action) + + assert.equal(resultingState.appState.warning, warningText, 'warning text set') + }); +}); diff --git a/test/unit/migrations-test.js b/test/unit/migrations-test.js index 3429ffb1d..092c0eccd 100644 --- a/test/unit/migrations-test.js +++ b/test/unit/migrations-test.js @@ -1,5 +1,4 @@ var assert = require('assert') -var test = require('tape') var path = require('path') var wallet1 = require(path.join('..', 'lib', 'migrations', '001.json')) diff --git a/test/unit/util_test.js b/test/unit/util_test.js new file mode 100644 index 000000000..7f8103d3b --- /dev/null +++ b/test/unit/util_test.js @@ -0,0 +1,114 @@ +var assert = require('assert') +var sinon = require('sinon') +const ethUtil = require('ethereumjs-util') + +var path = require('path') +var util = require(path.join(__dirname, '..', '..', 'ui', 'app', 'util.js')) + +describe('util', function() { + var ethInWei = '1' + for (var i = 0; i < 18; i++ ) { ethInWei += '0' } + + beforeEach(function() { + this.sinon = sinon.sandbox.create() + }) + + afterEach(function() { + this.sinon.restore() + }) + + describe('numericBalance', function() { + + it('should return a BN 0 if given nothing', function() { + var result = util.numericBalance() + assert.equal(result.toString(10), 0) + }) + + it('should work with hex prefix', function() { + var result = util.numericBalance('0x012') + assert.equal(result.toString(10), '18') + }) + + it('should work with no hex prefix', function() { + var result = util.numericBalance('012') + assert.equal(result.toString(10), '18') + }) + + }) + + describe('#ethToWei', function() { + + it('should take an eth BN, returns wei BN', function() { + var input = new ethUtil.BN(1, 10) + var result = util.ethToWei(input) + assert.equal(result, ethInWei, '18 zeroes') + }) + + }) + + describe('#weiToEth', function() { + + it('should take a wei BN and return an eth BN', function() { + var result = util.weiToEth(new ethUtil.BN(ethInWei)) + assert.equal(result, '1', 'equals 1 eth') + }) + + }) + + describe('#formatBalance', function() { + + it('when given nothing', function() { + var result = util.formatBalance() + assert.equal(result, 'None', 'should return "None"') + }) + + it('should return eth as string followed by ETH', function() { + var input = new ethUtil.BN(ethInWei, 10).toJSON() + var result = util.formatBalance(input) + assert.equal(result, '1.0000 ETH') + }) + + it('should return eth as string followed by ETH', function() { + var input = new ethUtil.BN(ethInWei, 10).div(new ethUtil.BN('2', 10)).toJSON() + var result = util.formatBalance(input) + assert.equal(result, '0.5000 ETH') + }) + + it('should display four decimal points', function() { + var input = "0x128dfa6a90b28000" + var result = util.formatBalance(input) + assert.equal(result, '1.3370 ETH') + }) + + }) + + describe('#normalizeToWei', function() { + it('should convert an eth to the appropriate equivalent values', function() { + var valueTable = { + wei: '1000000000000000000', + kwei: '1000000000000000', + mwei: '1000000000000', + gwei: '1000000000', + szabo: '1000000', + finney:'1000', + ether: '1', + kether:'0.001', + mether:'0.000001', + // AUDIT: We're getting BN numbers on these ones. + // I think they're big enough to ignore for now. + // gether:'0.000000001', + // tether:'0.000000000001', + } + var oneEthBn = new ethUtil.BN(ethInWei, 10) + + for(var currency in valueTable) { + + var value = new ethUtil.BN(valueTable[currency], 10) + var output = util.normalizeToWei(value, currency) + assert.equal(output.toString(10), valueTable.wei, `value of ${output.toString(10)} ${currency} should convert to ${oneEthBn}`) + + } + }) + }) + +}) diff --git a/ui/test/setup.js b/ui/test/setup.js deleted file mode 100644 index 7985e9a00..000000000 --- a/ui/test/setup.js +++ /dev/null @@ -1,8 +0,0 @@ -if (typeof process === 'object') { - // Initialize node environment - global.expect = require('chai').expect - require('mocha-jsdom')() -} else { - window.expect = window.chai.expect - window.require = function () { /* noop */ } -} diff --git a/ui/test/unit/actions/config_test.js b/ui/test/unit/actions/config_test.js deleted file mode 100644 index d38210bfc..000000000 --- a/ui/test/unit/actions/config_test.js +++ /dev/null @@ -1,43 +0,0 @@ -var jsdom = require('mocha-jsdom') -var assert = require('assert') -var freeze = require('deep-freeze-strict') -var path = require('path') - -var actions = require(path.join(__dirname, '..', '..', '..', 'app', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'app', 'reducers.js')) - -describe ('config view actions', function() { - - var initialState = { - metamask: { - rpcTarget: 'foo', - }, - appState: { - currentView: { - name: 'accounts', - } - } - } - freeze(initialState) - - describe('SHOW_CONFIG_PAGE', function() { - it('should set appState.currentView.name to config', function() { - var result = reducers(initialState, actions.showConfigPage()) - assert.equal(result.appState.currentView.name, 'config') - }) - }) - - describe('SET_RPC_TARGET', function() { - - it('sets the state.metamask.rpcTarget property of the state to the action.value', function() { - const action = { - type: actions.SET_RPC_TARGET, - value: 'bar', - } - - var result = reducers(initialState, action) - assert.equal(result.metamask.rpcTarget, action.value) - }) - }) -}) - diff --git a/ui/test/unit/actions/restore_vault_test.js b/ui/test/unit/actions/restore_vault_test.js deleted file mode 100644 index da0d71ce7..000000000 --- a/ui/test/unit/actions/restore_vault_test.js +++ /dev/null @@ -1,54 +0,0 @@ -var jsdom = require('mocha-jsdom') -var assert = require('assert') -var freeze = require('deep-freeze-strict') -var path = require('path') -var sinon = require('sinon') - -var actions = require(path.join(__dirname, '..', '..', '..', 'app', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'app', 'reducers.js')) - -describe('#recoverFromSeed(password, seed)', function() { - - beforeEach(function() { - // sinon allows stubbing methods that are easily verified - this.sinon = sinon.sandbox.create() - }) - - afterEach(function() { - // sinon requires cleanup otherwise it will overwrite context - this.sinon.restore() - }) - - // stub out account manager - actions._setAccountManager({ - recoverFromSeed(pw, seed, cb) { cb() }, - }) - - it('sets metamask.isUnlocked to true', function() { - var initialState = { - metamask: { - isUnlocked: false, - isInitialized: false, - } - } - freeze(initialState) - - const restorePhrase = 'invite heavy among daring outdoor dice jelly coil stable note seat vicious' - const password = 'foo' - const dispatchFunc = actions.recoverFromSeed(password, restorePhrase) - - var dispatchStub = this.sinon.stub() - dispatchStub.withArgs({ TYPE: actions.unlockMetamask() }).onCall(0) - dispatchStub.withArgs({ TYPE: actions.showAccountsPage() }).onCall(1) - - var action - var resultingState = initialState - dispatchFunc((newAction) => { - action = newAction - resultingState = reducers(resultingState, action) - }) - - assert.equal(resultingState.metamask.isUnlocked, true, 'was unlocked') - assert.equal(resultingState.metamask.isInitialized, true, 'was initialized') - }); -}); diff --git a/ui/test/unit/actions/set_selected_account_test.js b/ui/test/unit/actions/set_selected_account_test.js deleted file mode 100644 index 1af6c964f..000000000 --- a/ui/test/unit/actions/set_selected_account_test.js +++ /dev/null @@ -1,28 +0,0 @@ -var jsdom = require('mocha-jsdom') -var assert = require('assert') -var freeze = require('deep-freeze-strict') -var path = require('path') - -var actions = require(path.join(__dirname, '..', '..', '..', 'app', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'app', 'reducers.js')) - -describe('SET_SELECTED_ACCOUNT', function() { - - it('sets the state.appState.activeAddress property of the state to the action.value', function() { - var initialState = { - appState: { - activeAddress: 'foo', - } - } - freeze(initialState) - - const action = { - type: actions.SET_SELECTED_ACCOUNT, - value: 'bar', - } - freeze(action) - - var resultingState = reducers(initialState, action) - assert.equal(resultingState.appState.activeAddress, action.value) - }); -}); diff --git a/ui/test/unit/actions/tx_test.js b/ui/test/unit/actions/tx_test.js deleted file mode 100644 index d83ae16c0..000000000 --- a/ui/test/unit/actions/tx_test.js +++ /dev/null @@ -1,168 +0,0 @@ -var jsdom = require('mocha-jsdom') -var assert = require('assert') -var freeze = require('deep-freeze-strict') -var path = require('path') - -var actions = require(path.join(__dirname, '..', '..', '..', 'app', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'app', 'reducers.js')) - -describe('tx confirmation screen', function() { - var initialState, result - - describe('when there is only one tx', function() { - var firstTxId = 1457634084250832 - - beforeEach(function() { - - initialState = { - appState: { - currentView: { - name: 'confTx', - }, - }, - metamask: { - unconfTxs: { - '1457634084250832': { - id: 1457634084250832, - status: "unconfirmed", - time: 1457634084250, - } - }, - } - } - freeze(initialState) - }) - - describe('cancelTx', function() { - - before(function(done) { - actions._setAccountManager({ - approveTransaction(txId, cb) { cb('An error!') }, - cancelTransaction(txId) { /* noop */ }, - clearSeedWordCache(cb) { cb() }, - }) - - actions.cancelTx({id: firstTxId})(function(action) { - result = reducers(initialState, action) - done() - }) - }) - - it('should transition to the accounts list', function() { - assert.equal(result.appState.currentView.name, 'accounts') - }) - - it('should have no unconfirmed txs remaining', function() { - var count = getUnconfirmedTxCount(result) - assert.equal(count, 0) - }) - }) - - describe('sendTx', function() { - var result - - describe('when there is an error', function() { - - before(function(done) { - alert = () => {/* noop */} - - actions._setAccountManager({ - approveTransaction(txId, cb) { cb('An error!') }, - }) - - actions.sendTx({id: firstTxId})(function(action) { - result = reducers(initialState, action) - done() - }) - }) - - it('should stay on the page', function() { - assert.equal(result.appState.currentView.name, 'confTx') - }) - - it('should set errorMessage on the currentView', function() { - assert(result.appState.currentView.errorMessage) - }) - }) - - describe('when there is success', function() { - before(function(done) { - actions._setAccountManager({ - approveTransaction(txId, cb) { cb() }, - }) - - actions.sendTx({id: firstTxId})(function(action) { - result = reducers(initialState, action) - done() - }) - }) - - it('should navigate away from the tx page', function() { - assert.equal(result.appState.currentView.name, 'accounts') - }) - - it('should clear the tx from the unconfirmed transactions', function() { - assert(!(firstTxId in result.metamask.unconfTxs), 'tx is cleared') - }) - }) - }) - - describe('when there are two pending txs', function() { - var firstTxId = 1457634084250832 - var result, initialState - before(function(done) { - initialState = { - appState: { - currentView: { - name: 'confTx', - }, - }, - metamask: { - unconfTxs: { - '1457634084250832': { - id: 1457634084250832, - status: "unconfirmed", - time: 1457634084250, - }, - '1457634084250833': { - id: 1457634084250833, - status: "unconfirmed", - time: 1457634084255, - }, - }, - } - } - freeze(initialState) - - - actions._setAccountManager({ - approveTransaction(txId, cb) { cb() }, - }) - - actions.sendTx({id: firstTxId})(function(action) { - result = reducers(initialState, action) - done() - }) - }) - - it('should stay on the confTx view', function() { - assert.equal(result.appState.currentView.name, 'confTx') - }) - - it('should transition to the first tx', function() { - assert.equal(result.appState.currentView.context, 0) - }) - - it('should only have one unconfirmed tx remaining', function() { - var count = getUnconfirmedTxCount(result) - assert.equal(count, 1) - }) - }) - }) -}); - -function getUnconfirmedTxCount(state) { - var txs = state.metamask.unconfTxs - var count = Object.keys(txs).length - return count -} diff --git a/ui/test/unit/actions/view_info_test.js b/ui/test/unit/actions/view_info_test.js deleted file mode 100644 index 888712c67..000000000 --- a/ui/test/unit/actions/view_info_test.js +++ /dev/null @@ -1,23 +0,0 @@ -var jsdom = require('mocha-jsdom') -var assert = require('assert') -var freeze = require('deep-freeze-strict') -var path = require('path') - -var actions = require(path.join(__dirname, '..', '..', '..', 'app', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'app', 'reducers.js')) - -describe('SHOW_INFO_PAGE', function() { - - it('sets the state.appState.currentView.name property to info', function() { - var initialState = { - appState: { - activeAddress: 'foo', - } - } - freeze(initialState) - - const action = actions.showInfoPage() - var resultingState = reducers(initialState, action) - assert.equal(resultingState.appState.currentView.name, 'info') - }); -}); diff --git a/ui/test/unit/actions/warning_test.js b/ui/test/unit/actions/warning_test.js deleted file mode 100644 index eee198656..000000000 --- a/ui/test/unit/actions/warning_test.js +++ /dev/null @@ -1,24 +0,0 @@ -var jsdom = require('mocha-jsdom') -var assert = require('assert') -var freeze = require('deep-freeze-strict') -var path = require('path') - -var actions = require(path.join(__dirname, '..', '..', '..', 'app', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'app', 'reducers.js')) - -describe('action DISPLAY_WARNING', function() { - - it('sets appState.warning to provided value', function() { - var initialState = { - appState: {}, - } - freeze(initialState) - - const warningText = 'This is a sample warning message' - - const action = actions.displayWarning(warningText) - const resultingState = reducers(initialState, action) - - assert.equal(resultingState.appState.warning, warningText, 'warning text set') - }); -}); diff --git a/ui/test/unit/util_test.js b/ui/test/unit/util_test.js deleted file mode 100644 index b35d60812..000000000 --- a/ui/test/unit/util_test.js +++ /dev/null @@ -1,114 +0,0 @@ -var assert = require('assert') -var sinon = require('sinon') -const ethUtil = require('ethereumjs-util') - -var path = require('path') -var util = require(path.join(__dirname, '..', '..', 'app', 'util.js')) - -describe('util', function() { - var ethInWei = '1' - for (var i = 0; i < 18; i++ ) { ethInWei += '0' } - - beforeEach(function() { - this.sinon = sinon.sandbox.create() - }) - - afterEach(function() { - this.sinon.restore() - }) - - describe('numericBalance', function() { - - it('should return a BN 0 if given nothing', function() { - var result = util.numericBalance() - assert.equal(result.toString(10), 0) - }) - - it('should work with hex prefix', function() { - var result = util.numericBalance('0x012') - assert.equal(result.toString(10), '18') - }) - - it('should work with no hex prefix', function() { - var result = util.numericBalance('012') - assert.equal(result.toString(10), '18') - }) - - }) - - describe('#ethToWei', function() { - - it('should take an eth BN, returns wei BN', function() { - var input = new ethUtil.BN(1, 10) - var result = util.ethToWei(input) - assert.equal(result, ethInWei, '18 zeroes') - }) - - }) - - describe('#weiToEth', function() { - - it('should take a wei BN and return an eth BN', function() { - var result = util.weiToEth(new ethUtil.BN(ethInWei)) - assert.equal(result, '1', 'equals 1 eth') - }) - - }) - - describe('#formatBalance', function() { - - it('when given nothing', function() { - var result = util.formatBalance() - assert.equal(result, 'None', 'should return "None"') - }) - - it('should return eth as string followed by ETH', function() { - var input = new ethUtil.BN(ethInWei, 10).toJSON() - var result = util.formatBalance(input) - assert.equal(result, '1.0000 ETH') - }) - - it('should return eth as string followed by ETH', function() { - var input = new ethUtil.BN(ethInWei, 10).div(new ethUtil.BN('2', 10)).toJSON() - var result = util.formatBalance(input) - assert.equal(result, '0.5000 ETH') - }) - - it('should display four decimal points', function() { - var input = "0x128dfa6a90b28000" - var result = util.formatBalance(input) - assert.equal(result, '1.3370 ETH') - }) - - }) - - describe('#normalizeToWei', function() { - it('should convert an eth to the appropriate equivalent values', function() { - var valueTable = { - wei: '1000000000000000000', - kwei: '1000000000000000', - mwei: '1000000000000', - gwei: '1000000000', - szabo: '1000000', - finney:'1000', - ether: '1', - kether:'0.001', - mether:'0.000001', - // AUDIT: We're getting BN numbers on these ones. - // I think they're big enough to ignore for now. - // gether:'0.000000001', - // tether:'0.000000000001', - } - var oneEthBn = new ethUtil.BN(ethInWei, 10) - - for(var currency in valueTable) { - - var value = new ethUtil.BN(valueTable[currency], 10) - var output = util.normalizeToWei(value, currency) - assert.equal(output.toString(10), valueTable.wei, `value of ${output.toString(10)} ${currency} should convert to ${oneEthBn}`) - - } - }) - }) - -}) -- cgit From e949e6b11869e584e4f5e6684bd7c4d04b86ef0b Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 18 Apr 2016 11:49:06 -0700 Subject: contentscript - append inpage as first child --- app/scripts/contentscript.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 105f24988..a256a3f5b 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -7,7 +7,8 @@ var scriptTag = document.createElement('script') scriptTag.src = chrome.extension.getURL('scripts/inpage.js') scriptTag.onload = function() { this.parentNode.removeChild(this) } var container = document.head || document.documentElement -container.appendChild(scriptTag) +// append as first child +container.insertBefore(scriptTag, container.children[0]) // setup communication to page and plugin var pageStream = new LocalMessageDuplexStream({ -- cgit From 046bacbd5b7afe1ca414d3b982133cb45ff05639 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 18 Apr 2016 12:56:55 -0700 Subject: deps - remove unused faux-jax --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index db0e2823a..999ac806c 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,6 @@ "eth-store": "^1.1.0", "ethereumjs-tx": "^1.0.0", "ethereumjs-util": "^4.3.0", - "faux-jax": "git+https://github.com/kumavis/faux-jax.git#c3648de04804f3895c5b4972750cae5b51ddb103", "hat": "0.0.3", "inject-css": "^0.1.1", "metamask-logo": "^1.1.5", -- cgit From c503d0897d68470e7249e3a6d6294f9125e3598f Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 18 Apr 2016 12:57:42 -0700 Subject: deps - bump provider-engine to 7.4.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 999ac806c..0c84e2e3c 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "three.js": "^0.73.2", "through2": "^2.0.1", "web3": "^0.15.1", - "web3-provider-engine": "^7.2.1", + "web3-provider-engine": "^7.4.0", "xtend": "^4.0.1" }, "devDependencies": { -- cgit From a441e635bd5ae47a6f5b835becf3a40aaaab899d Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 18 Apr 2016 16:39:35 -0700 Subject: Persist transactions to config-manager Transactions are now stored, and are never deleted, they only have their status updated. We can add deleting later if we'd like. I've hacked on emitting the new unconfirmedTx key to the UI in the format it received before, I want Aaron's opinion on where I should actually do that. --- app/scripts/background.js | 4 +- app/scripts/lib/config-manager.js | 50 +++++++++++++++++++++++++ app/scripts/lib/idStore.js | 12 +++--- test/unit/config-manager-test.js | 79 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 138 insertions(+), 7 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index db4927083..bdf87b1a5 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -117,6 +117,7 @@ function storeSetFromObj(store, obj){ Object.keys(obj).forEach(function(key){ store.set(key, obj[key]) }) + store.set('unconfTxs', configManager.unconfirmedTxs()) } @@ -191,7 +192,8 @@ idStore.on('update', updateBadge) function updateBadge(state){ var label = '' - var count = Object.keys(state.unconfTxs).length + var unconfTxs = configManager.unconfirmedTxs() + var count = Object.keys(unconfTxs).length if (count) { label = String(count) } diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index f024729cc..356d53c22 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -134,6 +134,56 @@ ConfigManager.prototype.setData = function(data) { this.migrator.saveData(data) } +ConfigManager.prototype.getTxList = function() { + var data = this.migrator.getData() + if ('transactions' in data) { + return data.transactions + } else { + return [] + } +} + +ConfigManager.prototype._saveTxList = function(txList) { + var data = this.migrator.getData() + data.transactions = txList + this.setData(data) +} + +ConfigManager.prototype.addTx = function(tx) { + var transactions = this.getTxList() + transactions.push(tx) + this._saveTxList(transactions) +} + +ConfigManager.prototype.getTx = function(txId) { + var transactions = this.getTxList() + var matching = transactions.filter(tx => tx.id === txId) + return matching.length > 0 ? matching[0] : null +} + +ConfigManager.prototype.confirmTx = function(txId) { + this._setTxStatus(txId, 'confirmed') +} + +ConfigManager.prototype.rejectTx = function(txId) { + this._setTxStatus(txId, 'rejected') +} + +ConfigManager.prototype._setTxStatus = function(txId, status) { + var transactions = this.getTxList() + transactions.forEach((tx) => { + if (tx.id === txId) { + tx.status = status + } + }) + this._saveTxList(transactions) +} +ConfigManager.prototype.unconfirmedTxs = function() { + var transactions = this.getTxList() + return transactions.filter(tx => tx.status === 'unconfirmed') + .reduce((result, tx) => { result[tx.id] = tx; return result }, {}) +} + // observable ConfigManager.prototype.subscribe = function(fn){ diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js index f44300273..14e9b5769 100644 --- a/app/scripts/lib/idStore.js +++ b/app/scripts/lib/idStore.js @@ -31,7 +31,6 @@ function IdentityStore(ethStore) { this._currentState = { selectedAddress: null, identities: {}, - unconfTxs: {}, } // not part of serilized metamask state - only kept in memory this._unconfTxCbs = {} @@ -140,10 +139,11 @@ IdentityStore.prototype.addUnconfirmedTransaction = function(txParams, cb){ time: time, status: 'unconfirmed', } - this._currentState.unconfTxs[txId] = txData + configManager.addTx(txData) console.log('addUnconfirmedTransaction:', txData) // keep the cb around for after approval (requires user interaction) + // This cb fires completion to the Dapp's write operation. this._unconfTxCbs[txId] = cb // signal update @@ -154,7 +154,7 @@ IdentityStore.prototype.addUnconfirmedTransaction = function(txParams, cb){ // comes from metamask ui IdentityStore.prototype.approveTransaction = function(txId, cb){ - var txData = this._currentState.unconfTxs[txId] + var txData = configManager.getTx(txId) var txParams = txData.txParams var approvalCb = this._unconfTxCbs[txId] || noop @@ -162,20 +162,20 @@ IdentityStore.prototype.approveTransaction = function(txId, cb){ cb() approvalCb(null, true) // clean up - delete this._currentState.unconfTxs[txId] + configManager.confirmTx(txId) delete this._unconfTxCbs[txId] this._didUpdate() } // comes from metamask ui IdentityStore.prototype.cancelTransaction = function(txId){ - var txData = this._currentState.unconfTxs[txId] + var txData = configManager.getTx(txId) var approvalCb = this._unconfTxCbs[txId] || noop // reject tx approvalCb(null, false) // clean up - delete this._currentState.unconfTxs[txId] + configManager.rejectTx(txId) delete this._unconfTxCbs[txId] this._didUpdate() } diff --git a/test/unit/config-manager-test.js b/test/unit/config-manager-test.js index 10b6716d6..84632b0ea 100644 --- a/test/unit/config-manager-test.js +++ b/test/unit/config-manager-test.js @@ -68,4 +68,83 @@ describe('config-manager', function() { assert.equal(secondResult, secondRpc) }) }) + + describe('transactions', function() { + beforeEach(function() { + configManager._saveTxList([]) + }) + + describe('#getTxList', function() { + it('when new should return empty array', function() { + var result = configManager.getTxList() + assert.ok(Array.isArray(result)) + assert.equal(result.length, 0) + }) + }) + + describe('#_saveTxList', function() { + it('saves the submitted data to the tx list', function() { + var target = [{ foo: 'bar' }] + configManager._saveTxList(target) + var result = configManager.getTxList() + assert.equal(result[0].foo, 'bar') + }) + }) + + describe('#addTx', function() { + it('adds a tx returned in getTxList', function() { + var tx = { id: 1 } + configManager.addTx(tx) + var result = configManager.getTxList() + assert.ok(Array.isArray(result)) + assert.equal(result.length, 1) + assert.equal(result[0].id, 1) + }) + }) + + describe('#confirmTx', function() { + it('sets the tx status to confirmed', function() { + var tx = { id: 1, status: 'unconfirmed' } + configManager.addTx(tx) + configManager.confirmTx(1) + var result = configManager.getTxList() + assert.ok(Array.isArray(result)) + assert.equal(result.length, 1) + assert.equal(result[0].status, 'confirmed') + }) + }) + + describe('#rejectTx', function() { + it('sets the tx status to rejected', function() { + var tx = { id: 1, status: 'unconfirmed' } + configManager.addTx(tx) + configManager.rejectTx(1) + var result = configManager.getTxList() + assert.ok(Array.isArray(result)) + assert.equal(result.length, 1) + assert.equal(result[0].status, 'rejected') + }) + }) + + describe('#unconfirmedTxs', function() { + it('returns unconfirmed txs in a hash', function() { + configManager.addTx({ id: '1', status: 'unconfirmed' }) + configManager.addTx({ id: '2', status: 'confirmed' }) + let result = configManager.unconfirmedTxs() + assert.equal(typeof result, 'object') + assert.equal(result['1'].status, 'unconfirmed') + assert.equal(result['0'], undefined) + assert.equal(result['2'], undefined) + }) + }) + + describe('#getTx', function() { + it('returns a tx with the requested id', function() { + configManager.addTx({ id: '1', status: 'unconfirmed' }) + configManager.addTx({ id: '2', status: 'confirmed' }) + assert.equal(configManager.getTx('1').status, 'unconfirmed') + assert.equal(configManager.getTx('2').status, 'confirmed') + }) + }) + }) }) -- cgit From dc043b7f9bc717caddbc81e7f0bfcef86c44d751 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 18 Apr 2016 17:19:20 -0700 Subject: Fix method of emitting unconfirmedTxs to UI --- app/scripts/background.js | 1 - app/scripts/lib/idStore.js | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index bdf87b1a5..1519f63db 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -117,7 +117,6 @@ function storeSetFromObj(store, obj){ Object.keys(obj).forEach(function(key){ store.set(key, obj[key]) }) - store.set('unconfTxs', configManager.unconfirmedTxs()) } diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js index 14e9b5769..363ac817c 100644 --- a/app/scripts/lib/idStore.js +++ b/app/scripts/lib/idStore.js @@ -82,6 +82,7 @@ IdentityStore.prototype.getState = function(){ isInitialized: !!configManager.getWallet() && !seedWords, isUnlocked: this._isUnlocked(), seedWords: seedWords, + unconfTxs: configManager.unconfirmedTxs(), })) } -- cgit From cfdad0f9fec64bbc95a89b7ed4110f0b177ec17a Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 18 Apr 2016 17:19:58 -0700 Subject: Emit transaction list to UI --- app/scripts/lib/idStore.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js index 363ac817c..b451fd6d4 100644 --- a/app/scripts/lib/idStore.js +++ b/app/scripts/lib/idStore.js @@ -83,6 +83,7 @@ IdentityStore.prototype.getState = function(){ isUnlocked: this._isUnlocked(), seedWords: seedWords, unconfTxs: configManager.unconfirmedTxs(), + transactions: configManager.getTxList(), })) } -- cgit From 0e39110f3c0fb78b448b375d3211195f39e265a8 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 18 Apr 2016 17:25:03 -0700 Subject: Bump changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e74a095dd..dd66617a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Current Master +- Pending transactions are now persisted to localStorage and resume even after browser is closed. +- Completed transactions are now persisted and can be displayed via UI. + # 1.5.1 2016-04-15 - Corrected text above account list. Selected account is visible to all sites, not just the current domain. -- cgit From 1d56ab821e88863e6822c6924a5b39054756e80e Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 18 Apr 2016 17:35:42 -0700 Subject: Fix displayed RPC address Fixes #119 --- ui/app/config.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/app/config.js b/ui/app/config.js index 33d87bcc2..878c9955f 100644 --- a/ui/app/config.js +++ b/ui/app/config.js @@ -47,7 +47,6 @@ ConfigScreen.prototype.render = function() { currentProviderDisplay(metamaskState), - h('div', [ h('input', { placeholder: 'New RPC URL', @@ -95,7 +94,7 @@ ConfigScreen.prototype.render = function() { } function currentProviderDisplay(metamaskState) { - var rpc = metamaskState.rpcTarget + var rpc = metamaskState.provider.rpcTarget return h('div', [ h('h3', {style: { fontWeight: 'bold' }}, 'Currently using RPC'), h('p', rpc) -- cgit From 28f14661fd484f215c5bfdc93e1b447ddcdb42ca Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 18 Apr 2016 17:44:06 -0700 Subject: Bump changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd66617a1..6a4e7fd3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Pending transactions are now persisted to localStorage and resume even after browser is closed. - Completed transactions are now persisted and can be displayed via UI. +- Fix bug on config screen where current RPC address was always displayed wrong. # 1.5.1 2016-04-15 -- cgit