refactor(GUI): replace SET_SETTING with an atomic SET_SETTINGS action (#1515)

This commit is the first on a series of commit to incrementally
implement support for configuration files (so we avoid a huge PR like we
have at the moment).

Once of the first things we can do is replace the `SET_SETTING` redux
action with an atomic `SET_SETTINGS` action that sets all the settings
for the application at once.

The purpose of this change is that later the `SET_SETTINGS` action can
be modified to stringify all the settings and store them in a
configuration file, without having to deal with merges, conflicts, etc
(since the client application if forced to resolve those problems before
calling the `SET_SETTINGS` action.)

The behaviour of the code remains almost the same, with the exception
that the user can now set settings that we don't know about, so the user
can switch between Etcher versions without getting weird errors if one
of the configuration keys he has doesn't exist in the other version.

See: https://github.com/resin-io/etcher/pull/1382
Change-Type: patch
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
This commit is contained in:
Juan Cruz Viotti 2017-06-15 12:57:15 -04:00 committed by GitHub
parent a8f6275763
commit 103a048e4d
3 changed files with 52 additions and 32 deletions

View File

@ -20,7 +20,9 @@
* @module Etcher.Models.Settings * @module Etcher.Models.Settings
*/ */
const _ = require('lodash');
const Store = require('./store'); const Store = require('./store');
const errors = require('../../shared/errors');
/** /**
* @summary Set a setting value * @summary Set a setting value
@ -34,12 +36,25 @@ const Store = require('./store');
* settings.set('unmountOnSuccess', true); * settings.set('unmountOnSuccess', true);
*/ */
exports.set = (key, value) => { exports.set = (key, value) => {
if (_.isNil(key)) {
throw errors.createError({
title: 'Missing setting key'
});
}
if (!_.isString(key)) {
throw errors.createError({
title: `Invalid setting key: ${key}`
});
}
const newSettings = _.assign(exports.getAll(), {
[key]: value
});
Store.dispatch({ Store.dispatch({
type: Store.Actions.SET_SETTING, type: Store.Actions.SET_SETTINGS,
data: { data: newSettings
key,
value
}
}); });
}; };

View File

@ -79,7 +79,7 @@ const ACTIONS = _.fromPairs(_.map([
'SELECT_IMAGE', 'SELECT_IMAGE',
'REMOVE_DRIVE', 'REMOVE_DRIVE',
'REMOVE_IMAGE', 'REMOVE_IMAGE',
'SET_SETTING' 'SET_SETTINGS'
], (message) => { ], (message) => {
return [ message, message ]; return [ message, message ];
})); }));
@ -452,35 +452,40 @@ const storeReducer = (state = DEFAULT_STATE, action) => {
return state.deleteIn([ 'selection', 'image' ]); return state.deleteIn([ 'selection', 'image' ]);
} }
case ACTIONS.SET_SETTING: { case ACTIONS.SET_SETTINGS: {
const key = action.data.key; if (!action.data) {
const value = action.data.value;
if (!key) {
throw errors.createError({ throw errors.createError({
title: 'Missing setting key' title: 'Missing settings'
}); });
} }
if (!_.isString(key)) { if (!_.isPlainObject(action.data)) {
throw errors.createError({ throw errors.createError({
title: `Invalid setting key: ${key}` title: `Invalid settings: ${action.data}`
}); });
} }
if (!DEFAULT_STATE.get('settings').has(key)) { const invalidKey = _.find(_.keys(action.data), (key) => {
return !_.isString(key);
});
if (!_.isNil(invalidKey)) {
throw errors.createError({ throw errors.createError({
title: `Unsupported setting: ${key}` title: `Invalid setting key: ${invalidKey}`
}); });
} }
if (_.isObject(value)) { const invalidPair = _.find(_.toPairs(action.data), (pair) => {
return _.isObject(_.last(pair));
});
if (!_.isNil(invalidPair)) {
throw errors.createError({ throw errors.createError({
title: `Invalid setting value: ${value}` title: `Invalid setting value: ${_.last(invalidPair)} for ${_.first(invalidPair)}`
}); });
} }
return state.setIn([ 'settings', key ], value); return state.setIn([ 'settings' ], Immutable.fromJS(action.data));
} }
default: { default: {

View File

@ -9,20 +9,20 @@ describe('Browser: settings', function() {
describe('settings', function() { describe('settings', function() {
const SUPPORTED_KEYS = _.keys(Store.Defaults.get('settings').toJS()); const DEFAULT_KEYS = _.keys(Store.Defaults.get('settings').toJS());
beforeEach(function() { beforeEach(function() {
this.settings = settings.getAll(); this.settings = settings.getAll();
}); });
afterEach(function() { afterEach(function() {
_.each(SUPPORTED_KEYS, (supportedKey) => { _.each(DEFAULT_KEYS, (supportedKey) => {
settings.set(supportedKey, this.settings[supportedKey]); settings.set(supportedKey, this.settings[supportedKey]);
}); });
}); });
it('should be able to set and read values', function() { it('should be able to set and read values', function() {
const keyUnderTest = _.first(SUPPORTED_KEYS); const keyUnderTest = _.sample(DEFAULT_KEYS);
const originalValue = settings.get(keyUnderTest); const originalValue = settings.get(keyUnderTest);
settings.set(keyUnderTest, !originalValue); settings.set(keyUnderTest, !originalValue);
@ -33,10 +33,10 @@ describe('Browser: settings', function() {
describe('.set()', function() { describe('.set()', function() {
it('should throw if the key is not supported', function() { it('should set an unknown key', function() {
m.chai.expect(function() { m.chai.expect(settings.get('foobar')).to.be.undefined;
settings.set('foobar', true); settings.set('foobar', true);
}).to.throw('Unsupported setting: foobar'); m.chai.expect(settings.get('foobar')).to.be.true;
}); });
it('should throw if no key', function() { it('should throw if no key', function() {
@ -52,23 +52,23 @@ describe('Browser: settings', function() {
}); });
it('should throw if setting an object', function() { it('should throw if setting an object', function() {
const keyUnderTest = _.first(SUPPORTED_KEYS); const keyUnderTest = _.sample(DEFAULT_KEYS);
m.chai.expect(function() { m.chai.expect(function() {
settings.set(keyUnderTest, { settings.set(keyUnderTest, {
setting: 1 setting: 1
}); });
}).to.throw('Invalid setting value: [object Object]'); }).to.throw(`Invalid setting value: [object Object] for ${keyUnderTest}`);
}); });
it('should throw if setting an array', function() { it('should throw if setting an array', function() {
const keyUnderTest = _.first(SUPPORTED_KEYS); const keyUnderTest = _.sample(DEFAULT_KEYS);
m.chai.expect(function() { m.chai.expect(function() {
settings.set(keyUnderTest, [ 1, 2, 3 ]); settings.set(keyUnderTest, [ 1, 2, 3 ]);
}).to.throw('Invalid setting value: 1,2,3'); }).to.throw(`Invalid setting value: 1,2,3 for ${keyUnderTest}`);
}); });
it('should set the key to undefined if no value', function() { it('should set the key to undefined if no value', function() {
const keyUnderTest = _.first(SUPPORTED_KEYS); const keyUnderTest = _.sample(DEFAULT_KEYS);
settings.set(keyUnderTest); settings.set(keyUnderTest);
m.chai.expect(settings.get(keyUnderTest)).to.be.undefined; m.chai.expect(settings.get(keyUnderTest)).to.be.undefined;
}); });
@ -80,7 +80,7 @@ describe('Browser: settings', function() {
it('should be able to read all values', function() { it('should be able to read all values', function() {
const allValues = settings.getAll(); const allValues = settings.getAll();
_.each(SUPPORTED_KEYS, function(supportedKey) { _.each(DEFAULT_KEYS, function(supportedKey) {
m.chai.expect(allValues[supportedKey]).to.equal(settings.get(supportedKey)); m.chai.expect(allValues[supportedKey]).to.equal(settings.get(supportedKey));
}); });
}); });