Feature selector (#4074)
* WIP * ParityBar verification * import from index.js * i18n expansion & tests * Features component * Adapt language selector to use features * Add features to settings view * typo * Convert logging * Fix earlier merge issues resulting in test failures * Lint failure fixes (new rules) * Fix additional listing rules * Re-add FormattedMessage (missing after merge), fix tests * Fix loader overrides * grumble: split item rendering (& test) * grumble: allow enable/disable while testing (default on) * grumble: move LanguageSelector below Features * grumble: don't pass visiblity prop (& update tests) * grumble: missing observable (onClick misbehaving) * grumble: don't reset to defaults per session * Fix to single store instance
This commit is contained in:
@@ -16,17 +16,18 @@
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { Link } from 'react-router';
|
||||
import { connect } from 'react-redux';
|
||||
import { throttle } from 'lodash';
|
||||
import store from 'store';
|
||||
|
||||
import imagesEthcoreBlock from '~/../assets/images/parity-logo-white-no-text.svg';
|
||||
import { CancelIcon, FingerprintIcon } from '~/ui/Icons';
|
||||
import { Badge, Button, ContainerTitle, ParityBackground } from '~/ui';
|
||||
import { Embedded as Signer } from '../Signer';
|
||||
import DappsStore from '~/views/Dapps/dappsStore';
|
||||
|
||||
import imagesEthcoreBlock from '!url-loader!../../../assets/images/parity-logo-white-no-text.svg';
|
||||
import styles from './parityBar.css';
|
||||
|
||||
const LS_STORE_KEY = '_parity::parityBar';
|
||||
@@ -112,10 +113,6 @@ class ParityBar extends Component {
|
||||
render () {
|
||||
const { moving, opened, position } = this.state;
|
||||
|
||||
const content = opened
|
||||
? this.renderExpanded()
|
||||
: this.renderBar();
|
||||
|
||||
const containerClassNames = opened
|
||||
? [ styles.overlay ]
|
||||
: [ styles.bar ];
|
||||
@@ -124,11 +121,12 @@ class ParityBar extends Component {
|
||||
containerClassNames.push(styles.moving);
|
||||
}
|
||||
|
||||
const parityBgClassName = opened
|
||||
? styles.expanded
|
||||
: styles.corner;
|
||||
|
||||
const parityBgClassNames = [ parityBgClassName, styles.parityBg ];
|
||||
const parityBgClassNames = [
|
||||
opened
|
||||
? styles.expanded
|
||||
: styles.corner,
|
||||
styles.parityBg
|
||||
];
|
||||
|
||||
if (moving) {
|
||||
parityBgClassNames.push(styles.moving);
|
||||
@@ -169,7 +167,11 @@ class ParityBar extends Component {
|
||||
ref='container'
|
||||
style={ parityBgStyle }
|
||||
>
|
||||
{ content }
|
||||
{
|
||||
opened
|
||||
? this.renderExpanded()
|
||||
: this.renderBar()
|
||||
}
|
||||
</ParityBackground>
|
||||
</div>
|
||||
);
|
||||
@@ -182,34 +184,38 @@ class ParityBar extends Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parityIcon = (
|
||||
<img
|
||||
src={ imagesEthcoreBlock }
|
||||
className={ styles.parityIcon }
|
||||
/>
|
||||
);
|
||||
|
||||
const parityButton = (
|
||||
<Button
|
||||
className={ styles.parityButton }
|
||||
icon={ parityIcon }
|
||||
label={ this.renderLabel('Parity') }
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ styles.cornercolor }
|
||||
ref={ this.onRef }
|
||||
>
|
||||
{ this.renderLink(parityButton) }
|
||||
{
|
||||
this.renderLink(
|
||||
<Button
|
||||
className={ styles.parityButton }
|
||||
icon={
|
||||
<img
|
||||
className={ styles.parityIcon }
|
||||
src={ imagesEthcoreBlock }
|
||||
/>
|
||||
}
|
||||
label={
|
||||
this.renderLabel(
|
||||
<FormattedMessage
|
||||
id='parityBar.label.parity'
|
||||
defaultMessage='Parity'
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<Button
|
||||
className={ styles.button }
|
||||
icon={ <FingerprintIcon /> }
|
||||
label={ this.renderSignerLabel() }
|
||||
onClick={ this.toggleDisplay }
|
||||
/>
|
||||
|
||||
{ this.renderDrag() }
|
||||
</div>
|
||||
);
|
||||
@@ -307,7 +313,13 @@ class ParityBar extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
return this.renderLabel('Signer', bubble);
|
||||
return this.renderLabel(
|
||||
<FormattedMessage
|
||||
id='parityBar.label.signer'
|
||||
defaultMessage='Signer'
|
||||
/>,
|
||||
bubble
|
||||
);
|
||||
}
|
||||
|
||||
getHorizontal (x) {
|
||||
|
||||
157
js/src/views/ParityBar/parityBar.spec.js
Normal file
157
js/src/views/ParityBar/parityBar.spec.js
Normal file
@@ -0,0 +1,157 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import ParityBar from './';
|
||||
|
||||
let component;
|
||||
let instance;
|
||||
let store;
|
||||
|
||||
function createRedux (state = {}) {
|
||||
store = {
|
||||
dispatch: sinon.stub(),
|
||||
subscribe: sinon.stub(),
|
||||
getState: () => Object.assign({ signer: { pending: [] } }, state)
|
||||
};
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
function render (props = {}, state = {}) {
|
||||
component = shallow(
|
||||
<ParityBar { ...props } />,
|
||||
{
|
||||
context: {
|
||||
store: createRedux(state)
|
||||
}
|
||||
}
|
||||
).find('ParityBar').shallow({ context: { api: {} } });
|
||||
instance = component.instance();
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('views/ParityBar', () => {
|
||||
beforeEach(() => {
|
||||
render({ dapp: true });
|
||||
});
|
||||
|
||||
it('renders defaults', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
|
||||
it('includes the ParityBackground', () => {
|
||||
expect(component.find('Connect(ParityBackground)')).to.have.length(1);
|
||||
});
|
||||
|
||||
describe('renderBar', () => {
|
||||
let bar;
|
||||
|
||||
beforeEach(() => {
|
||||
bar = shallow(instance.renderBar());
|
||||
});
|
||||
|
||||
it('renders nothing when not overlaying a dapp', () => {
|
||||
render({ dapp: false });
|
||||
expect(instance.renderBar()).to.be.null;
|
||||
});
|
||||
|
||||
it('renders when overlaying a dapp', () => {
|
||||
expect(bar.find('div')).not.to.have.length(0);
|
||||
});
|
||||
|
||||
it('renders the Parity button', () => {
|
||||
const label = shallow(bar.find('Button').first().props().label);
|
||||
|
||||
expect(label.find('FormattedMessage').props().id).to.equal('parityBar.label.parity');
|
||||
});
|
||||
|
||||
it('renders the Signer button', () => {
|
||||
const label = shallow(bar.find('Button').last().props().label);
|
||||
|
||||
expect(label.find('FormattedMessage').props().id).to.equal('parityBar.label.signer');
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderExpanded', () => {
|
||||
let expanded;
|
||||
|
||||
beforeEach(() => {
|
||||
expanded = shallow(instance.renderExpanded());
|
||||
});
|
||||
|
||||
it('includes the Signer', () => {
|
||||
expect(expanded.find('Connect(Embedded)')).to.have.length(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderLabel', () => {
|
||||
it('renders the label name', () => {
|
||||
expect(shallow(instance.renderLabel('testing', null)).text()).to.equal('testing');
|
||||
});
|
||||
|
||||
it('renders name and bubble', () => {
|
||||
expect(shallow(instance.renderLabel('testing', '(bubble)')).text()).to.equal('testing(bubble)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderSignerLabel', () => {
|
||||
let label;
|
||||
|
||||
beforeEach(() => {
|
||||
label = shallow(instance.renderSignerLabel());
|
||||
});
|
||||
|
||||
it('renders the signer label', () => {
|
||||
expect(label.find('FormattedMessage').props().id).to.equal('parityBar.label.signer');
|
||||
});
|
||||
|
||||
it('does not render a badge when no pending requests', () => {
|
||||
expect(label.find('Badge')).to.have.length(0);
|
||||
});
|
||||
|
||||
it('renders a badge when pending requests', () => {
|
||||
render({}, { signer: { pending: ['123', '456'] } });
|
||||
expect(shallow(instance.renderSignerLabel()).find('Badge').props().value).to.equal(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('opened state', () => {
|
||||
beforeEach(() => {
|
||||
sinon.spy(instance, 'renderBar');
|
||||
sinon.spy(instance, 'renderExpanded');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
instance.renderBar.restore();
|
||||
instance.renderExpanded.restore();
|
||||
});
|
||||
|
||||
it('renders the bar on with opened === false', () => {
|
||||
expect(component.find('Link[to="/apps"]')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('renders expanded with opened === true', () => {
|
||||
expect(instance.renderExpanded).not.to.have.been.called;
|
||||
instance.setState({ opened: true });
|
||||
expect(instance.renderExpanded).to.have.been.called;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -14,67 +14,28 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { MenuItem } from 'material-ui';
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { MenuItem } from 'material-ui';
|
||||
import LogLevel from 'loglevel';
|
||||
|
||||
import { LOG_KEYS } from '~/config';
|
||||
import { Select, Container, LanguageSelector } from '~/ui';
|
||||
import Features, { FeaturesStore, FEATURES } from '~/ui/Features';
|
||||
|
||||
import Store, { LOGLEVEL_OPTIONS } from './store';
|
||||
import layout from '../layout.css';
|
||||
|
||||
@observer
|
||||
export default class Parity extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
state = {
|
||||
loglevels: {},
|
||||
mode: 'active',
|
||||
selectValues: []
|
||||
};
|
||||
store = new Store(this.context.api);
|
||||
features = FeaturesStore.get();
|
||||
|
||||
componentWillMount () {
|
||||
this.loadMode();
|
||||
this.loadLogLevels();
|
||||
this.setSelectValues();
|
||||
}
|
||||
|
||||
loadLogLevels () {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const nextState = { ...this.state.logLevels };
|
||||
|
||||
Object.keys(LOG_KEYS).map((logKey) => {
|
||||
const log = LOG_KEYS[logKey];
|
||||
|
||||
const logger = LogLevel.getLogger(log.key);
|
||||
const level = logger.getLevel();
|
||||
|
||||
nextState[logKey] = { level, log };
|
||||
});
|
||||
|
||||
this.setState({ logLevels: nextState });
|
||||
}
|
||||
|
||||
setSelectValues () {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const selectValues = Object.keys(LogLevel.levels).map((levelName) => {
|
||||
const value = LogLevel.levels[levelName];
|
||||
|
||||
return {
|
||||
name: levelName,
|
||||
value
|
||||
};
|
||||
});
|
||||
|
||||
this.setState({ selectValues });
|
||||
return this.store.loadMode();
|
||||
}
|
||||
|
||||
render () {
|
||||
@@ -94,18 +55,30 @@ export default class Parity extends Component {
|
||||
</div>
|
||||
</div>
|
||||
<div className={ layout.details }>
|
||||
<LanguageSelector />
|
||||
{ this.renderModes() }
|
||||
<Features />
|
||||
<LanguageSelector />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ this.renderLogsConfig() }
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
renderItem (mode, label) {
|
||||
return (
|
||||
<MenuItem
|
||||
key={ mode }
|
||||
label={ label }
|
||||
value={ mode }
|
||||
>
|
||||
{ label }
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
renderLogsConfig () {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
if (!this.features.active[FEATURES.LOGLEVELS]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -127,129 +100,89 @@ export default class Parity extends Component {
|
||||
}
|
||||
|
||||
renderLogsLevels () {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
return null;
|
||||
}
|
||||
const { logLevels } = this.store;
|
||||
|
||||
const { logLevels, selectValues } = this.state;
|
||||
return Object
|
||||
.keys(logLevels)
|
||||
.map((key) => {
|
||||
const { level, log } = logLevels[key];
|
||||
const { path, desc } = log;
|
||||
|
||||
return Object.keys(logLevels).map((logKey) => {
|
||||
const { level, log } = logLevels[logKey];
|
||||
const { key, desc } = log;
|
||||
const onChange = (_, index) => {
|
||||
this.store.updateLoggerLevel(path, Object.values(LOGLEVEL_OPTIONS)[index].value);
|
||||
};
|
||||
|
||||
const onChange = (_, index) => {
|
||||
const nextLevel = Object.values(selectValues)[index].value;
|
||||
|
||||
LogLevel.getLogger(key).setLevel(nextLevel);
|
||||
this.loadLogLevels();
|
||||
};
|
||||
|
||||
return (
|
||||
<div key={ logKey }>
|
||||
<p>{ desc }</p>
|
||||
<Select
|
||||
onChange={ onChange }
|
||||
value={ level }
|
||||
values={ selectValues }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<div key={ key }>
|
||||
<p>{ desc }</p>
|
||||
<Select
|
||||
onChange={ onChange }
|
||||
value={ level }
|
||||
values={ LOGLEVEL_OPTIONS }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
renderModes () {
|
||||
const { mode } = this.state;
|
||||
|
||||
const renderItem = (mode, label) => {
|
||||
return (
|
||||
<MenuItem
|
||||
key={ mode }
|
||||
value={ mode }
|
||||
label={ label }
|
||||
>
|
||||
{ label }
|
||||
</MenuItem>
|
||||
);
|
||||
};
|
||||
const { mode } = this.store;
|
||||
|
||||
return (
|
||||
<Select
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='settings.parity.modes.label'
|
||||
defaultMessage='mode of operation'
|
||||
/>
|
||||
}
|
||||
id='parityModeSelect'
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='settings.parity.modes.hint'
|
||||
defaultMessage='the syning mode for the Parity node'
|
||||
/>
|
||||
}
|
||||
value={ mode }
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='settings.parity.modes.label'
|
||||
defaultMessage='mode of operation'
|
||||
/>
|
||||
}
|
||||
onChange={ this.onChangeMode }
|
||||
value={ mode }
|
||||
>
|
||||
{
|
||||
renderItem('active',
|
||||
this.renderItem('active', (
|
||||
<FormattedMessage
|
||||
id='settings.parity.modes.mode_active'
|
||||
defaultMessage='Parity continuously syncs the chain'
|
||||
/>
|
||||
)
|
||||
))
|
||||
}
|
||||
{
|
||||
renderItem('passive',
|
||||
this.renderItem('passive', (
|
||||
<FormattedMessage
|
||||
id='settings.parity.modes.mode_passive'
|
||||
defaultMessage='Parity syncs initially, then sleeps and wakes regularly to resync'
|
||||
/>
|
||||
)
|
||||
))
|
||||
}
|
||||
{
|
||||
renderItem('dark',
|
||||
this.renderItem('dark', (
|
||||
<FormattedMessage
|
||||
id='settings.parity.modes.mode_dark'
|
||||
defaultMessage='Parity syncs only when the RPC is active'
|
||||
/>
|
||||
)
|
||||
))
|
||||
}
|
||||
{
|
||||
renderItem('offline',
|
||||
this.renderItem('offline', (
|
||||
<FormattedMessage
|
||||
id='settings.parity.modes.mode_offline'
|
||||
defaultMessage="Parity doesn't sync"
|
||||
/>
|
||||
)
|
||||
))
|
||||
}
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
onChangeMode = (event, index, mode) => {
|
||||
const { api } = this.context;
|
||||
|
||||
api.parity
|
||||
.setMode(mode)
|
||||
.then((result) => {
|
||||
if (result) {
|
||||
this.setState({ mode });
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('onChangeMode', error);
|
||||
});
|
||||
}
|
||||
|
||||
loadMode () {
|
||||
const { api } = this.context;
|
||||
|
||||
api.parity
|
||||
.mode()
|
||||
.then((mode) => {
|
||||
this.setState({ mode });
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('loadMode', error);
|
||||
});
|
||||
this.store.changeMode(mode || event.target.value);
|
||||
}
|
||||
}
|
||||
|
||||
98
js/src/views/Settings/Parity/parity.spec.js
Normal file
98
js/src/views/Settings/Parity/parity.spec.js
Normal file
@@ -0,0 +1,98 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { createApi } from './parity.test.js';
|
||||
import Parity from './';
|
||||
|
||||
let component;
|
||||
let instance;
|
||||
|
||||
function render (props = {}) {
|
||||
component = shallow(
|
||||
<Parity { ...props } />,
|
||||
{ context: { api: createApi() } }
|
||||
);
|
||||
instance = component.instance();
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('views/Settings/Parity', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
sinon.spy(instance.store, 'loadMode');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
instance.store.loadMode.restore();
|
||||
});
|
||||
|
||||
it('renders defaults', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
|
||||
describe('componentWillMount', () => {
|
||||
beforeEach(() => {
|
||||
return instance.componentWillMount();
|
||||
});
|
||||
|
||||
it('loads the mode in the store', () => {
|
||||
expect(instance.store.loadMode).to.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
describe('components', () => {
|
||||
it('renders a Container component', () => {
|
||||
expect(component.find('Container')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('renders a LanguageSelector component', () => {
|
||||
expect(component.find('LanguageSelector')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('renders a Features component', () => {
|
||||
expect(component.find('LanguageSelector')).to.have.length(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Parity features', () => {
|
||||
describe('mode selector', () => {
|
||||
let select;
|
||||
|
||||
beforeEach(() => {
|
||||
select = component.find('Select[id="parityModeSelect"]');
|
||||
sinon.spy(instance.store, 'changeMode');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
instance.store.changeMode.restore();
|
||||
});
|
||||
|
||||
it('renders a mode selector', () => {
|
||||
expect(select).to.have.length(1);
|
||||
});
|
||||
|
||||
it('changes the mode on the store when changed', () => {
|
||||
select.simulate('change', { target: { value: 'dark' } });
|
||||
expect(instance.store.changeMode).to.have.been.calledWith('dark');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
30
js/src/views/Settings/Parity/parity.test.js
Normal file
30
js/src/views/Settings/Parity/parity.test.js
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sinon from 'sinon';
|
||||
|
||||
function createApi () {
|
||||
return {
|
||||
parity: {
|
||||
mode: sinon.stub().resolves('passive'),
|
||||
setMode: sinon.stub().resolves(true)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
createApi
|
||||
};
|
||||
105
js/src/views/Settings/Parity/store.js
Normal file
105
js/src/views/Settings/Parity/store.js
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import LogLevel from 'loglevel';
|
||||
import { action, observable } from 'mobx';
|
||||
|
||||
import { LOG_KEYS } from '~/config';
|
||||
|
||||
const DEFAULT_MODE = 'active';
|
||||
const LOGLEVEL_OPTIONS = Object
|
||||
.keys(LogLevel.levels)
|
||||
.map((name) => {
|
||||
return {
|
||||
name,
|
||||
value: LogLevel.levels[name]
|
||||
};
|
||||
});
|
||||
|
||||
export default class Store {
|
||||
@observable logLevels = {};
|
||||
@observable mode = DEFAULT_MODE;
|
||||
|
||||
constructor (api) {
|
||||
this._api = api;
|
||||
|
||||
this.loadLogLevels();
|
||||
}
|
||||
|
||||
@action setLogLevels = (logLevels) => {
|
||||
this.logLevels = logLevels;
|
||||
}
|
||||
|
||||
@action setLogLevelsSelect = (logLevelsSelect) => {
|
||||
this.logLevelsSelect = logLevelsSelect;
|
||||
}
|
||||
|
||||
@action setMode = (mode) => {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
changeMode (mode) {
|
||||
return this._api.parity
|
||||
.setMode(mode)
|
||||
.then((result) => {
|
||||
if (result) {
|
||||
this.setMode(mode);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('changeMode', error);
|
||||
});
|
||||
}
|
||||
|
||||
loadLogLevels () {
|
||||
this.setLogLevels(
|
||||
Object
|
||||
.keys(LOG_KEYS)
|
||||
.reduce((state, logKey) => {
|
||||
const log = LOG_KEYS[logKey];
|
||||
const logger = LogLevel.getLogger(log.key);
|
||||
const level = logger.getLevel();
|
||||
|
||||
state[logKey] = {
|
||||
level,
|
||||
log
|
||||
};
|
||||
|
||||
return state;
|
||||
}, this.logLevels)
|
||||
);
|
||||
}
|
||||
|
||||
updateLoggerLevel (path, level) {
|
||||
LogLevel.getLogger(path).setLevel(level);
|
||||
this.loadLogLevels();
|
||||
}
|
||||
|
||||
loadMode () {
|
||||
return this._api.parity
|
||||
.mode()
|
||||
.then((mode) => {
|
||||
this.setMode(mode);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('loadMode', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
LOGLEVEL_OPTIONS
|
||||
};
|
||||
84
js/src/views/Settings/Parity/store.spec.js
Normal file
84
js/src/views/Settings/Parity/store.spec.js
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { createApi } from './parity.test.js';
|
||||
import Store from './store';
|
||||
|
||||
let api;
|
||||
let store;
|
||||
|
||||
function createStore () {
|
||||
api = createApi();
|
||||
store = new Store(api);
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
describe('views/Settings/Parity/Store', () => {
|
||||
beforeEach(() => {
|
||||
createStore();
|
||||
sinon.spy(store, 'setMode');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
store.setMode.restore();
|
||||
});
|
||||
|
||||
it('defaults to mode === active', () => {
|
||||
expect(store.mode).to.equal('active');
|
||||
});
|
||||
|
||||
describe('@action', () => {
|
||||
describe('setMode', () => {
|
||||
it('sets the mode', () => {
|
||||
store.setMode('offline');
|
||||
expect(store.mode).to.equal('offline');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('operations', () => {
|
||||
describe('changeMode', () => {
|
||||
beforeEach(() => {
|
||||
return store.changeMode('offline');
|
||||
});
|
||||
|
||||
it('calls parity.setMode', () => {
|
||||
expect(api.parity.setMode).to.have.been.calledWith('offline');
|
||||
});
|
||||
|
||||
it('sets the mode as provided', () => {
|
||||
expect(store.setMode).to.have.been.calledWith('offline');
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadMode', () => {
|
||||
beforeEach(() => {
|
||||
return store.loadMode();
|
||||
});
|
||||
|
||||
it('calls parity.mode', () => {
|
||||
expect(api.parity.mode).to.have.been.called;
|
||||
});
|
||||
|
||||
it('sets the mode as retrieved', () => {
|
||||
expect(store.setMode).to.have.been.calledWith('passive');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user