diff --git a/js/src/i18n/constants.js b/js/src/i18n/constants.js
new file mode 100644
index 000000000..7b863be62
--- /dev/null
+++ b/js/src/i18n/constants.js
@@ -0,0 +1,27 @@
+// 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 .
+
+const DEFAULT_LOCALE = 'en';
+const DEFAULT_LOCALES = process.env.NODE_ENV === 'production'
+ ? ['en']
+ : ['en', 'de'];
+const LS_STORE_KEY = '_parity::locale';
+
+export {
+ DEFAULT_LOCALE,
+ DEFAULT_LOCALES,
+ LS_STORE_KEY
+};
diff --git a/js/src/i18n/languages.spec.js b/js/src/i18n/languages.spec.js
new file mode 100644
index 000000000..2f33bf3b6
--- /dev/null
+++ b/js/src/i18n/languages.spec.js
@@ -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 .
+
+import { DEFAULT_LOCALE } from './constants';
+import languages from './languages';
+
+const keys = Object.keys(languages);
+
+describe('i18n/languages', () => {
+ it('has a language list', () => {
+ expect(keys.length > 1).to.be.true;
+ });
+
+ it('includes DEFAULT_LOCALE as a language', () => {
+ expect(keys.includes(DEFAULT_LOCALE)).to.be.true;
+ });
+});
diff --git a/js/src/i18n/store.js b/js/src/i18n/store.js
index 497e55c72..f5a5f93ed 100644
--- a/js/src/i18n/store.js
+++ b/js/src/i18n/store.js
@@ -21,39 +21,32 @@ import de from 'react-intl/locale-data/de';
import en from 'react-intl/locale-data/en';
import store from 'store';
+import { DEFAULT_LOCALE, DEFAULT_LOCALES, LS_STORE_KEY } from './constants';
import languages from './languages';
import deMessages from './de';
import enMessages from './en';
-const LS_STORE_KEY = '_parity::locale';
-
let instance = null;
-const isProduction = process.env.NODE_ENV === 'production';
-const DEFAULT = 'en';
const LANGUAGES = flatten({ languages });
const MESSAGES = {
de: Object.assign(flatten(deMessages), LANGUAGES),
en: Object.assign(flatten(enMessages), LANGUAGES)
};
-const LOCALES = isProduction
- ? ['en']
- : ['en', 'de'];
addLocaleData([...de, ...en]);
export default class Store {
- @observable locale = DEFAULT;
- @observable locales = LOCALES;
- @observable messages = MESSAGES[DEFAULT];
- @observable isDevelopment = !isProduction;
+ @observable locale = DEFAULT_LOCALE;
+ @observable locales = DEFAULT_LOCALES;
+ @observable messages = MESSAGES[DEFAULT_LOCALE];
constructor () {
const savedLocale = store.get(LS_STORE_KEY);
- this.locale = (savedLocale && LOCALES.includes(savedLocale))
+ this.locale = (savedLocale && DEFAULT_LOCALES.includes(savedLocale))
? savedLocale
- : DEFAULT;
+ : DEFAULT_LOCALE;
this.messages = MESSAGES[this.locale];
}
diff --git a/js/src/i18n/store.spec.js b/js/src/i18n/store.spec.js
new file mode 100644
index 000000000..d6dbc4fba
--- /dev/null
+++ b/js/src/i18n/store.spec.js
@@ -0,0 +1,73 @@
+// 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 .
+
+import store from 'store';
+
+import { DEFAULT_LOCALE, DEFAULT_LOCALES, LS_STORE_KEY } from './constants';
+import { LocaleStore } from './';
+
+let localeStore;
+
+describe('i18n/Store', () => {
+ before(() => {
+ localeStore = LocaleStore.get();
+ store.set(LS_STORE_KEY, 'testing');
+ });
+
+ it('creates a default instance', () => {
+ expect(localeStore).to.be.ok;
+ });
+
+ it('sets the default locale to default (invalid localStorage)', () => {
+ expect(localeStore.locale).to.equal(DEFAULT_LOCALE);
+ });
+
+ it('loads the locale from localStorage (valid localStorage)', () => {
+ const testLocale = DEFAULT_LOCALES[DEFAULT_LOCALES.length - 1];
+
+ store.set(LS_STORE_KEY, testLocale);
+
+ const testStore = new LocaleStore();
+
+ expect(testStore.locale).to.equal(testLocale);
+ });
+
+ it('lists the locales', () => {
+ expect(localeStore.locales.length > 1).to.be.true;
+ });
+
+ it('lists locals including default', () => {
+ expect(localeStore.locales.includes(DEFAULT_LOCALE)).to.be.true;
+ });
+
+ describe('@action', () => {
+ describe('setLocale', () => {
+ const testLocale = DEFAULT_LOCALES[DEFAULT_LOCALES.length - 1];
+
+ beforeEach(() => {
+ localeStore.setLocale(testLocale);
+ });
+
+ it('sets the locale as passed', () => {
+ expect(localeStore.locale).to.equal(testLocale);
+ });
+
+ it('sets the locale in localStorage', () => {
+ expect(store.get(LS_STORE_KEY)).to.equal(testLocale);
+ });
+ });
+ });
+});
diff --git a/js/src/ui/Features/constants.js b/js/src/ui/Features/constants.js
new file mode 100644
index 000000000..4bcf16c99
--- /dev/null
+++ b/js/src/ui/Features/constants.js
@@ -0,0 +1,21 @@
+// 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 .
+
+const LS_STORE_KEY = '_parity::features';
+
+export {
+ LS_STORE_KEY
+};
diff --git a/js/src/ui/Features/defaults.js b/js/src/ui/Features/defaults.js
new file mode 100644
index 000000000..60122e2fa
--- /dev/null
+++ b/js/src/ui/Features/defaults.js
@@ -0,0 +1,61 @@
+// 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 .
+
+const MODES = {
+ DEVELOPMENT: 1000, // only in dev mode, disabled by default, can be toggled
+ TESTING: 1011, // feature is available in dev mode
+ PRODUCTION: 1022 // feature is available
+};
+
+const FEATURES = {
+ LANGUAGE: 'LANGUAGE',
+ LOGLEVELS: 'LOGLEVELS'
+};
+
+const DEFAULTS = {
+ [FEATURES.LANGUAGE]: {
+ mode: MODES.TESTING,
+ name: 'Language Selection',
+ description: 'Allows changing the default interface language'
+ },
+ [FEATURES.LOGLEVELS]: {
+ mode: MODES.TESTING,
+ name: 'Logging Level Selection',
+ description: 'Allows changing of the log levels for various components'
+ }
+};
+
+if (process.env.NODE_ENV === 'test') {
+ Object
+ .keys(MODES)
+ .forEach((mode) => {
+ const key = `.${mode}`;
+
+ FEATURES[key] = key;
+ DEFAULTS[key] = {
+ mode: MODES[mode],
+ name: key,
+ description: key
+ };
+ });
+}
+
+export default DEFAULTS;
+
+export {
+ FEATURES,
+ MODES
+};
diff --git a/js/src/ui/Features/defaults.spec.js b/js/src/ui/Features/defaults.spec.js
new file mode 100644
index 000000000..df7f4253a
--- /dev/null
+++ b/js/src/ui/Features/defaults.spec.js
@@ -0,0 +1,67 @@
+// 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 .
+
+import defaults, { FEATURES, MODES } from './defaults';
+
+const features = Object.values(FEATURES);
+const modes = Object.values(MODES);
+
+describe('ui/Features/Defaults', () => {
+ describe('feature codes', () => {
+ Object.keys(FEATURES).forEach((key) => {
+ describe(key, () => {
+ let value;
+
+ beforeEach(() => {
+ value = FEATURES[key];
+ });
+
+ it('exists as an default', () => {
+ expect(defaults[value]).to.be.ok;
+ });
+
+ it('has a single unique code', () => {
+ expect(features.filter((code) => code === value).length).to.equal(1);
+ });
+ });
+ });
+ });
+
+ describe('defaults', () => {
+ Object.keys(defaults).forEach((key) => {
+ describe(key, () => {
+ let value;
+
+ beforeEach(() => {
+ value = defaults[key];
+ });
+
+ it('exists as an exposed feature', () => {
+ expect(features.includes(key)).to.be.ok;
+ });
+
+ it('has a valid mode', () => {
+ expect(modes.includes(value.mode)).to.be.true;
+ });
+
+ it('has a name and description', () => {
+ expect(value.description).to.be.ok;
+ expect(value.name).to.be.ok;
+ });
+ });
+ });
+ });
+});
diff --git a/js/src/ui/Features/features.css b/js/src/ui/Features/features.css
new file mode 100644
index 000000000..c5d370153
--- /dev/null
+++ b/js/src/ui/Features/features.css
@@ -0,0 +1,20 @@
+/* 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 .
+*/
+
+.description {
+ opacity: 0.75;
+}
diff --git a/js/src/ui/Features/features.js b/js/src/ui/Features/features.js
new file mode 100644
index 000000000..bae1ffcd8
--- /dev/null
+++ b/js/src/ui/Features/features.js
@@ -0,0 +1,69 @@
+// 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 .
+
+import { Checkbox } from 'material-ui';
+import { observer } from 'mobx-react';
+import { List, ListItem } from 'material-ui/List';
+import React, { Component } from 'react';
+
+import defaults, { MODES } from './defaults';
+import Store from './store';
+import styles from './features.css';
+
+@observer
+export default class Features extends Component {
+ store = Store.get();
+
+ render () {
+ if (process.env.NODE_ENV === 'production') {
+ return null;
+ }
+
+ return (
+
+ {
+ Object
+ .keys(defaults)
+ .filter((key) => defaults[key].mode !== MODES.PRODUCTION)
+ .map(this.renderItem)
+ }
+
+ );
+ }
+
+ renderItem = (key) => {
+ const feature = defaults[key];
+ const onCheck = () => this.store.toggleActive(key);
+
+ return (
+
+ }
+ primaryText={ feature.name }
+ secondaryText={
+
+ { feature.description }
+
+ }
+ />
+ );
+ }
+}
diff --git a/js/src/ui/Features/features.spec.js b/js/src/ui/Features/features.spec.js
new file mode 100644
index 000000000..412505507
--- /dev/null
+++ b/js/src/ui/Features/features.spec.js
@@ -0,0 +1,89 @@
+// 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 .
+
+import { shallow } from 'enzyme';
+import React from 'react';
+
+import defaults, { MODES } from './defaults';
+
+import Features from './';
+
+let component;
+let instance;
+
+function render (props = { visible: true }) {
+ component = shallow(
+
+ );
+ instance = component.instance();
+
+ return component;
+}
+
+describe('views/Settings/Features', () => {
+ beforeEach(() => {
+ render();
+ });
+
+ it('renders defaults', () => {
+ expect(component).to.be.ok;
+ });
+
+ describe('visibility', () => {
+ let oldEnv;
+
+ beforeEach(() => {
+ oldEnv = process.env.NODE_ENV;
+ });
+
+ afterEach(() => {
+ process.env.NODE_ENV = oldEnv;
+ });
+
+ it('renders null when NODE_ENV === production', () => {
+ process.env.NODE_ENV = 'production';
+ render();
+ expect(component.get(0)).to.be.null;
+ });
+
+ it('renders component when NODE_ENV !== production', () => {
+ process.env.NODE_ENV = 'development';
+ render();
+ expect(component.get(0)).not.to.be.null;
+ });
+ });
+
+ describe('instance methods', () => {
+ describe('renderItem', () => {
+ const keys = Object.keys(defaults).filter((key) => defaults[key].mode !== MODES.PRODUCTION);
+ const key = keys[0];
+
+ let item;
+
+ beforeEach(() => {
+ item = instance.renderItem(key);
+ });
+
+ it('renders an item', () => {
+ expect(item).not.to.be.null;
+ });
+
+ it('displays the correct name', () => {
+ expect(item.props.primaryText).to.equal(defaults[key].name);
+ });
+ });
+ });
+});
diff --git a/js/src/ui/Features/index.js b/js/src/ui/Features/index.js
new file mode 100644
index 000000000..97b2b25c7
--- /dev/null
+++ b/js/src/ui/Features/index.js
@@ -0,0 +1,25 @@
+// 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 .
+
+import { FEATURES } from './defaults';
+import FeaturesStore from './store';
+
+export default from './features';
+
+export {
+ FEATURES,
+ FeaturesStore
+};
diff --git a/js/src/ui/Features/store.js b/js/src/ui/Features/store.js
new file mode 100644
index 000000000..263e6375c
--- /dev/null
+++ b/js/src/ui/Features/store.js
@@ -0,0 +1,73 @@
+// 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 .
+
+import { action, observable } from 'mobx';
+import store from 'store';
+
+import { LS_STORE_KEY } from './constants';
+import defaults, { FEATURES, MODES } from './defaults';
+
+const isProductionMode = process.env.NODE_ENV === 'production';
+
+let instance = null;
+
+export default class Store {
+ @observable active = {};
+
+ constructor () {
+ this.loadActiveFeatures();
+ }
+
+ @action setActiveFeatures = (features = {}, isProduction) => {
+ this.active = Object.assign({}, this.getDefaultActive(isProduction), features);
+ }
+
+ @action toggleActive = (featureKey) => {
+ this.active = Object.assign({}, this.active, { [featureKey]: !this.active[featureKey] });
+ this.saveActiveFeatures();
+ }
+
+ getDefaultActive (isProduction = isProductionMode) {
+ const modesTest = [MODES.PRODUCTION];
+
+ if (!isProduction) {
+ modesTest.push(MODES.TESTING);
+ }
+
+ return Object
+ .keys(FEATURES)
+ .reduce((visibility, feature) => {
+ visibility[feature] = modesTest.includes(defaults[feature].mode);
+ return visibility;
+ }, {});
+ }
+
+ loadActiveFeatures () {
+ this.setActiveFeatures(store.get(LS_STORE_KEY));
+ }
+
+ saveActiveFeatures () {
+ store.set(LS_STORE_KEY, this.active);
+ }
+
+ static get () {
+ if (!instance) {
+ instance = new Store();
+ }
+
+ return instance;
+ }
+}
diff --git a/js/src/ui/Features/store.spec.js b/js/src/ui/Features/store.spec.js
new file mode 100644
index 000000000..53b05c6d6
--- /dev/null
+++ b/js/src/ui/Features/store.spec.js
@@ -0,0 +1,96 @@
+// 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 .
+
+import lstore from 'store';
+
+import { LS_STORE_KEY } from './constants';
+import defaults, { MODES } from './defaults';
+
+import Store from './store';
+
+let store;
+
+function createStore () {
+ store = new Store();
+
+ return store;
+}
+
+describe('ui/Features/Store', () => {
+ beforeEach(() => {
+ lstore.set(LS_STORE_KEY, { 'testingFromStorage': true });
+ createStore();
+ });
+
+ it('loads with values from localStorage', () => {
+ expect(store.active.testingFromStorage).to.be.true;
+ });
+
+ describe('@action', () => {
+ describe('setActiveFeatures', () => {
+ it('sets the active features', () => {
+ store.setActiveFeatures({ 'testing': true });
+ expect(store.active.testing).to.be.true;
+ });
+
+ it('overrides the defaults', () => {
+ store.setActiveFeatures({ '.PRODUCTION': false });
+ expect(store.active['.PRODUCTION']).to.be.false;
+ });
+ });
+
+ describe('toggleActive', () => {
+ it('changes the state of a feature', () => {
+ expect(store.active['.PRODUCTION']).to.be.true;
+ store.toggleActive('.PRODUCTION');
+ expect(store.active['.PRODUCTION']).to.be.false;
+ });
+
+ it('saves the updated state to localStorage', () => {
+ store.toggleActive('.PRODUCTION');
+ expect(lstore.get(LS_STORE_KEY)).to.deep.equal(store.active);
+ });
+ });
+ });
+
+ describe('operations', () => {
+ describe('getDefaultActive', () => {
+ it('returns features where mode === TESTING|PRODUCTION (non-production)', () => {
+ const visibility = store.getDefaultActive(false);
+
+ expect(
+ Object
+ .keys(visibility)
+ .filter((key) => visibility[key])
+ .filter((key) => ![MODES.TESTING, MODES.PRODUCTION].includes(defaults[key].mode))
+ .length
+ ).to.equal(0);
+ });
+
+ it('returns features where mode === PRODUCTION (production)', () => {
+ const visibility = store.getDefaultActive(true);
+
+ expect(
+ Object
+ .keys(visibility)
+ .filter((key) => visibility[key])
+ .filter((key) => ![MODES.PRODUCTION].includes(defaults[key].mode))
+ .length
+ ).to.equal(0);
+ });
+ });
+ });
+});
diff --git a/js/src/ui/LanguageSelector/languageSelector.js b/js/src/ui/LanguageSelector/languageSelector.js
index ddaa086d7..70cf2dafa 100644
--- a/js/src/ui/LanguageSelector/languageSelector.js
+++ b/js/src/ui/LanguageSelector/languageSelector.js
@@ -14,20 +14,23 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see .
-import React, { Component } from 'react';
-import { FormattedMessage } from 'react-intl';
import { MenuItem } from 'material-ui';
import { observer } from 'mobx-react';
+import React, { Component } from 'react';
+import { FormattedMessage } from 'react-intl';
+
+import { LocaleStore } from '~/i18n';
+import { FeaturesStore, FEATURES } from '../Features';
import Select from '../Form/Select';
-import { LocaleStore } from '../../i18n';
@observer
export default class LanguageSelector extends Component {
+ features = FeaturesStore.get();
store = LocaleStore.get();
render () {
- if (!this.store.isDevelopment) {
+ if (!this.features.active[FEATURES.LANGUAGE]) {
return null;
}
@@ -70,6 +73,6 @@ export default class LanguageSelector extends Component {
}
onChange = (event, index, locale) => {
- this.store.setLocale(locale);
+ this.store.setLocale(locale || event.target.value);
}
}
diff --git a/js/src/ui/LanguageSelector/langugeSelector.spec.js b/js/src/ui/LanguageSelector/langugeSelector.spec.js
new file mode 100644
index 000000000..285f6557d
--- /dev/null
+++ b/js/src/ui/LanguageSelector/langugeSelector.spec.js
@@ -0,0 +1,69 @@
+// 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 .
+
+import { shallow } from 'enzyme';
+import React from 'react';
+import sinon from 'sinon';
+
+import { LocaleStore } from '~/i18n';
+
+import LanguageSelector from './';
+
+let component;
+
+function render (props = {}) {
+ component = shallow(
+
+ );
+
+ return component;
+}
+
+describe('LanguageSelector', () => {
+ it('renders defaults', () => {
+ expect(render()).to.be.ok;
+ });
+
+ describe('Select', () => {
+ let select;
+ let localeStore;
+
+ beforeEach(() => {
+ localeStore = LocaleStore.get();
+ sinon.stub(localeStore, 'setLocale');
+
+ render();
+ select = component.find('Select');
+ });
+
+ afterEach(() => {
+ localeStore.setLocale.restore();
+ });
+
+ it('renders the Select', () => {
+ expect(select).to.have.length(1);
+ });
+
+ it('has locale items', () => {
+ expect(select.find('MenuItem').length > 0).to.be.true;
+ });
+
+ it('calls localeStore.setLocale when changed', () => {
+ select.simulate('change', { target: { value: 'de' } });
+ expect(localeStore.setLocale).to.have.been.calledWith('de');
+ });
+ });
+});
diff --git a/js/src/ui/index.js b/js/src/ui/index.js
index 33001b84b..d66663d8e 100644
--- a/js/src/ui/index.js
+++ b/js/src/ui/index.js
@@ -31,6 +31,7 @@ import CopyToClipboard from './CopyToClipboard';
import CurrencySymbol from './CurrencySymbol';
import Editor from './Editor';
import Errors from './Errors';
+import Features, { FEATURES, FeaturesStore } from './Features';
import Form, { AddressSelect, FormWrap, TypedInput, Input, InputAddress, InputAddressSelect, InputChip, InputInline, Select, RadioButtons } from './Form';
import GasPriceEditor from './GasPriceEditor';
import GasPriceSelector from './GasPriceSelector';
@@ -74,6 +75,9 @@ export {
CurrencySymbol,
Editor,
Errors,
+ FEATURES,
+ Features,
+ FeaturesStore,
Form,
FormWrap,
GasPriceEditor,
diff --git a/js/src/views/ParityBar/parityBar.js b/js/src/views/ParityBar/parityBar.js
index 3cc342e12..eec3577c9 100644
--- a/js/src/views/ParityBar/parityBar.js
+++ b/js/src/views/ParityBar/parityBar.js
@@ -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()
+ }
);
@@ -182,34 +184,38 @@ class ParityBar extends Component {
return null;
}
- const parityIcon = (
-
- );
-
- const parityButton = (
-
- );
-
return (
+ );
+ });
}
renderModes () {
- const { mode } = this.state;
-
- const renderItem = (mode, label) => {
- return (
-
- );
- };
+ const { mode } = this.store;
return (
- }
+ id='parityModeSelect'
hint={
}
- value={ mode }
+ label={
+
+ }
onChange={ this.onChangeMode }
+ value={ mode }
>
{
- renderItem('active',
+ this.renderItem('active', (
- )
+ ))
}
{
- renderItem('passive',
+ this.renderItem('passive', (
- )
+ ))
}
{
- renderItem('dark',
+ this.renderItem('dark', (
- )
+ ))
}
{
- renderItem('offline',
+ this.renderItem('offline', (
- )
+ ))
}
);
}
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);
}
}
diff --git a/js/src/views/Settings/Parity/parity.spec.js b/js/src/views/Settings/Parity/parity.spec.js
new file mode 100644
index 000000000..32fc6d467
--- /dev/null
+++ b/js/src/views/Settings/Parity/parity.spec.js
@@ -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 .
+
+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(
+ ,
+ { 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');
+ });
+ });
+ });
+});
diff --git a/js/src/views/Settings/Parity/parity.test.js b/js/src/views/Settings/Parity/parity.test.js
new file mode 100644
index 000000000..8238fcac9
--- /dev/null
+++ b/js/src/views/Settings/Parity/parity.test.js
@@ -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 .
+
+import sinon from 'sinon';
+
+function createApi () {
+ return {
+ parity: {
+ mode: sinon.stub().resolves('passive'),
+ setMode: sinon.stub().resolves(true)
+ }
+ };
+}
+
+export {
+ createApi
+};
diff --git a/js/src/views/Settings/Parity/store.js b/js/src/views/Settings/Parity/store.js
new file mode 100644
index 000000000..6a1394a0c
--- /dev/null
+++ b/js/src/views/Settings/Parity/store.js
@@ -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 .
+
+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
+};
diff --git a/js/src/views/Settings/Parity/store.spec.js b/js/src/views/Settings/Parity/store.spec.js
new file mode 100644
index 000000000..663298c0b
--- /dev/null
+++ b/js/src/views/Settings/Parity/store.spec.js
@@ -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 .
+
+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');
+ });
+ });
+ });
+});
diff --git a/js/webpack/app.js b/js/webpack/app.js
index 2c28dcdbf..f3bd890e4 100644
--- a/js/webpack/app.js
+++ b/js/webpack/app.js
@@ -117,8 +117,13 @@ module.exports = {
test: /\.(woff(2)|ttf|eot|otf)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: [ 'file-loader?name=fonts/[name][hash:10].[ext]' ]
},
+ {
+ test: /parity-logo-white-no-text\.svg/,
+ use: [ 'url-loader' ]
+ },
{
test: /\.svg(\?v=[0-9]\.[0-9]\.[0-9])?$/,
+ exclude: [ /parity-logo-white-no-text\.svg/ ],
use: [ 'file-loader?name=assets/[name].[hash:10].[ext]' ]
}
],