Chain-selection from UI (#4859)

* First little bits for chain-selection.

* Provide RPCs and get settings through to user defaults.

* Hasty stash.

* Fix updater accidentally redownloading.

* Finish up.

* Add JS tests.

* Hypervisor should never run a binary modified before itself.

* Style.

* Help tweak.

* Fix test compile.

* Fix JS test

* Build fix for tests.

* Revert default chain name

* Another test

* Use spec name via client.

* Fix mock up.

* whitespace

[ci:skip]

* whitespace

[ci:skip]

* remove exit/restart endpoints.
This commit is contained in:
Gav Wood
2017-03-13 12:10:53 +01:00
committed by GitHub
parent 8a67a0a80a
commit 3041c95408
28 changed files with 372 additions and 50 deletions

View File

@@ -35,6 +35,7 @@ export default class Parity extends Component {
features = FeaturesStore.get();
componentWillMount () {
this.store.loadChain();
return this.store.loadMode();
}
@@ -50,11 +51,12 @@ export default class Parity extends Component {
<div>
<FormattedMessage
id='settings.parity.overview_0'
defaultMessage='Control the Parity node settings and mode of operation via this interface.'
defaultMessage='Control the Parity node settings and nature of syncing via this interface.'
/>
</div>
</div>
<div className={ layout.details }>
{ this.renderChains() }
{ this.renderModes() }
<Features />
<LanguageSelector />
@@ -65,12 +67,12 @@ export default class Parity extends Component {
);
}
renderItem (mode, label) {
renderItem (name, label) {
return (
<MenuItem
key={ mode }
key={ name }
label={ label }
value={ mode }
value={ name }
>
{ label }
</MenuItem>
@@ -134,7 +136,7 @@ export default class Parity extends Component {
hint={
<FormattedMessage
id='settings.parity.modes.hint'
defaultMessage='the syning mode for the Parity node'
defaultMessage='the syncing mode for the Parity node'
/>
}
label={
@@ -182,7 +184,100 @@ export default class Parity extends Component {
);
}
renderChains () {
const { chain } = this.store;
return (
<Select
id='parityChainSelect'
hint={
<FormattedMessage
id='settings.parity.chains.hint'
defaultMessage='the chain for the Parity node to sync to'
/>
}
label={
<FormattedMessage
id='settings.parity.chains.label'
defaultMessage='chain/network to sync'
/>
}
onChange={ this.onChangeChain }
value={ chain }
>
{
this.renderItem('foundation', (
<FormattedMessage
id='settings.parity.chains.chain_foundation'
defaultMessage='Parity syncs to the Ethereum network launched by the Ethereum Foundation'
/>
))
}
{
this.renderItem('kovan', (
<FormattedMessage
id='settings.parity.chains.chain_kovan'
defaultMessage='Parity syncs to the Kovan test network'
/>
))
}
{
this.renderItem('olympic', (
<FormattedMessage
id='settings.parity.chains.chain_olympic'
defaultMessage='Parity syncs to the Olympic test network'
/>
))
}
{
this.renderItem('morden', (
<FormattedMessage
id='settings.parity.chains.cmorden_kovan'
defaultMessage='Parity syncs to Morden (Classic) test network'
/>
))
}
{
this.renderItem('ropsten', (
<FormattedMessage
id='settings.parity.chains.chain_ropsten'
defaultMessage='Parity syncs to the Ropsten test network'
/>
))
}
{
this.renderItem('classic', (
<FormattedMessage
id='settings.parity.chains.chain_classic'
defaultMessage='Parity syncs to the Ethereum Classic network'
/>
))
}
{
this.renderItem('expanse', (
<FormattedMessage
id='settings.parity.chains.chain_expanse'
defaultMessage='Parity syncs to the Expanse network'
/>
))
}
{
this.renderItem('dev', (
<FormattedMessage
id='settings.parity.chains.chain_dev'
defaultMessage='Parity uses a local development chain'
/>
))
}
</Select>
);
}
onChangeMode = (event, index, mode) => {
this.store.changeMode(mode || event.target.value);
}
onChangeChain = (event, index, chain) => {
this.store.changeChain(chain || event.target.value);
}
}

View File

@@ -38,10 +38,12 @@ describe('views/Settings/Parity', () => {
beforeEach(() => {
render();
sinon.spy(instance.store, 'loadMode');
sinon.spy(instance.store, 'loadChain');
});
afterEach(() => {
instance.store.loadMode.restore();
instance.store.loadChain.restore();
});
it('renders defaults', () => {
@@ -56,6 +58,10 @@ describe('views/Settings/Parity', () => {
it('loads the mode in the store', () => {
expect(instance.store.loadMode).to.have.been.called;
});
it('loads the chain in the store', () => {
expect(instance.store.loadChain).to.have.been.called;
});
});
describe('components', () => {
@@ -94,5 +100,27 @@ describe('views/Settings/Parity', () => {
expect(instance.store.changeMode).to.have.been.calledWith('dark');
});
});
describe('chain selector', () => {
let select;
beforeEach(() => {
select = component.find('Select[id="parityChainSelect"]');
sinon.spy(instance.store, 'changeChain');
});
afterEach(() => {
instance.store.changeChain.restore();
});
it('renders a chain selector', () => {
expect(select).to.have.length(1);
});
it('changes the chain on the store when changed', () => {
select.simulate('change', { target: { value: 'dark' } });
expect(instance.store.changeChain).to.have.been.calledWith('dark');
});
});
});
});

View File

@@ -20,7 +20,9 @@ function createApi () {
return {
parity: {
mode: sinon.stub().resolves('passive'),
setMode: sinon.stub().resolves(true)
setMode: sinon.stub().resolves(true),
chain: sinon.stub().resolves('foundation'),
setChain: sinon.stub().resolves(true)
}
};
}

View File

@@ -20,6 +20,7 @@ import { action, observable } from 'mobx';
import { LOG_KEYS } from '~/config';
const DEFAULT_MODE = 'active';
const DEFAULT_CHAIN = 'foundation';
const LOGLEVEL_OPTIONS = Object
.keys(LogLevel.levels)
.map((name) => {
@@ -32,6 +33,7 @@ const LOGLEVEL_OPTIONS = Object
export default class Store {
@observable logLevels = {};
@observable mode = DEFAULT_MODE;
@observable chain = DEFAULT_CHAIN;
constructor (api) {
this._api = api;
@@ -51,6 +53,10 @@ export default class Store {
this.mode = mode;
}
@action setChain = (chain) => {
this.chain = chain;
}
changeMode (mode) {
return this._api.parity
.setMode(mode)
@@ -64,6 +70,19 @@ export default class Store {
});
}
changeChain (chain) {
return this._api.parity
.setChain(chain)
.then((result) => {
if (result) {
this.setChain(chain);
}
})
.catch((error) => {
console.warn('changeChain', error);
});
}
loadLogLevels () {
this.setLogLevels(
Object
@@ -98,6 +117,17 @@ export default class Store {
console.warn('loadMode', error);
});
}
loadChain () {
return this._api.parity
.chain()
.then((chain) => {
this.setChain(chain);
})
.catch((error) => {
console.warn('loadChain', error);
});
}
}
export {

View File

@@ -33,16 +33,22 @@ describe('views/Settings/Parity/Store', () => {
beforeEach(() => {
createStore();
sinon.spy(store, 'setMode');
sinon.spy(store, 'setChain');
});
afterEach(() => {
store.setMode.restore();
store.setChain.restore();
});
it('defaults to mode === active', () => {
expect(store.mode).to.equal('active');
});
it('defaults to chain === foundation', () => {
expect(store.chain).to.equal('foundation');
});
describe('@action', () => {
describe('setMode', () => {
it('sets the mode', () => {
@@ -50,6 +56,13 @@ describe('views/Settings/Parity/Store', () => {
expect(store.mode).to.equal('offline');
});
});
describe('setChain', () => {
it('sets the chain', () => {
store.setChain('dev');
expect(store.chain).to.equal('dev');
});
});
});
describe('operations', () => {
@@ -80,5 +93,33 @@ describe('views/Settings/Parity/Store', () => {
expect(store.setMode).to.have.been.calledWith('passive');
});
});
describe('changeChain', () => {
beforeEach(() => {
return store.changeChain('dev');
});
it('calls parity.setChain', () => {
expect(api.parity.setChain).to.have.been.calledWith('dev');
});
it('sets the chain as provided', () => {
expect(store.setChain).to.have.been.calledWith('dev');
});
});
describe('loadChain', () => {
beforeEach(() => {
return store.loadChain();
});
it('calls parity.chain', () => {
expect(api.parity.chain).to.have.been.called;
});
it('sets the chain as retrieved', () => {
expect(store.setChain).to.have.been.calledWith('foundation');
});
});
});
});