From fbdec04786e28bad45021bef4a74e7077e34282f Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Thu, 4 Jan 2018 18:55:24 +0900 Subject: move settings validator --- src/shared/settings/validator.js | 61 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/shared/settings/validator.js (limited to 'src/shared/settings') diff --git a/src/shared/settings/validator.js b/src/shared/settings/validator.js new file mode 100644 index 0000000..949ab29 --- /dev/null +++ b/src/shared/settings/validator.js @@ -0,0 +1,61 @@ +import operations from 'shared/operations'; + +const VALID_TOP_KEYS = ['keymaps', 'search', 'blacklist']; +const VALID_OPERATION_VALUES = Object.keys(operations).map((key) => { + return operations[key]; +}); + +const validateInvalidTopKeys = (settings) => { + let invalidKey = Object.keys(settings).find((key) => { + return !VALID_TOP_KEYS.includes(key); + }); + if (invalidKey) { + throw Error(`Unknown key: "${invalidKey}"`); + } +}; + +const validateKeymaps = (keymaps) => { + for (let key of Object.keys(keymaps)) { + let value = keymaps[key]; + if (!VALID_OPERATION_VALUES.includes(value.type)) { + throw Error(`Unknown operation: "${value.type}"`); + } + } +}; + +const validateSearch = (search) => { + let engines = search.engines; + for (let key of Object.keys(engines)) { + if (/\s/.test(key)) { + throw new Error( + `While space in search engine name is not allowed: "${key}"` + ); + } + let url = engines[key]; + if (!url.match(/{}/)) { + throw new Error(`No {}-placeholders in URL of "${key}"`); + } + if (url.match(/{}/g).length > 1) { + throw new Error(`Multiple {}-placeholders in URL of "${key}"`); + } + } + + if (!search.default) { + throw new Error(`Default engine is not set`); + } + if (!Object.keys(engines).includes(search.default)) { + throw new Error(`Default engine "${search.default}" not found`); + } +}; + +const validate = (settings) => { + validateInvalidTopKeys(settings); + if (settings.keymaps) { + validateKeymaps(settings.keymaps); + } + if (settings.search) { + validateSearch(settings.search); + } +}; + +export { validate }; -- cgit v1.2.3 From e19f89f16248ed4bf28deaa9ab4b5204061df5eb Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Thu, 4 Jan 2018 20:34:33 +0900 Subject: add property to settings --- src/shared/settings/property-types.js | 6 ++++++ src/shared/settings/validator.js | 17 ++++++++++++++++- src/shared/settings/values.js | 18 ++++++++++++------ test/shared/settings/values.test.js | 29 +++++++++++++++++++++++++++-- 4 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 src/shared/settings/property-types.js (limited to 'src/shared/settings') diff --git a/src/shared/settings/property-types.js b/src/shared/settings/property-types.js new file mode 100644 index 0000000..bcfa809 --- /dev/null +++ b/src/shared/settings/property-types.js @@ -0,0 +1,6 @@ +export default { + // TODO describe property types here + // mystr: 'string', + // mynum: 'number', + // mybool: 'boolean', +}; diff --git a/src/shared/settings/validator.js b/src/shared/settings/validator.js index 949ab29..6fadac7 100644 --- a/src/shared/settings/validator.js +++ b/src/shared/settings/validator.js @@ -1,6 +1,7 @@ import operations from 'shared/operations'; +import propertyTypes from './property-types'; -const VALID_TOP_KEYS = ['keymaps', 'search', 'blacklist']; +const VALID_TOP_KEYS = ['keymaps', 'search', 'blacklist', 'properties']; const VALID_OPERATION_VALUES = Object.keys(operations).map((key) => { return operations[key]; }); @@ -48,6 +49,17 @@ const validateSearch = (search) => { } }; +const validateProperties = (properties) => { + for (let name of Object.keys(properties)) { + if (!propertyTypes[name]) { + throw new Error(`Unknown property name: "${name}"`); + } + if (typeof properties[name] !== propertyTypes[name]) { + throw new Error(`Invalid type for property: "${name}"`); + } + } +}; + const validate = (settings) => { validateInvalidTopKeys(settings); if (settings.keymaps) { @@ -56,6 +68,9 @@ const validate = (settings) => { if (settings.search) { validateSearch(settings.search); } + if (settings.properties) { + validateProperties(settings.properties); + } }; export { validate }; diff --git a/src/shared/settings/values.js b/src/shared/settings/values.js index 4e55fa0..5027ba5 100644 --- a/src/shared/settings/values.js +++ b/src/shared/settings/values.js @@ -44,9 +44,12 @@ const valueFromForm = (form) => { } } - let blacklist = form.blacklist; - - return { keymaps, search, blacklist }; + return { + keymaps, + search, + blacklist: form.blacklist, + properties: form.properties + }; }; const jsonFromValue = (value) => { @@ -78,9 +81,12 @@ const formFromValue = (value, allowedOps) => { } } - let blacklist = value.blacklist; - - return { keymaps, search, blacklist }; + return { + keymaps, + search, + blacklist: value.blacklist, + properties: value.properties, + }; }; const jsonFromForm = (form) => { diff --git a/test/shared/settings/values.test.js b/test/shared/settings/values.test.js index 2632cd7..62cfb5f 100644 --- a/test/shared/settings/values.test.js +++ b/test/shared/settings/values.test.js @@ -7,13 +7,21 @@ describe("settings values", () => { let json = `{ "keymaps": { "0": {"type": "scroll.home"}}, "search": { "default": "google", "engines": { "google": "https://google.com/search?q={}" }}, - "blacklist": [ "*.slack.com"] + "blacklist": [ "*.slack.com"], + "properties": { + "mystr": "value", + "mynum": 123, + "mybool": true + } }`; let value = values.valueFromJson(json); expect(value.keymaps).to.deep.equal({ 0: {type: "scroll.home"}}); expect(value.search).to.deep.equal({ default: "google", engines: { google: "https://google.com/search?q={}"} }); expect(value.blacklist).to.deep.equal(["*.slack.com"]); + expect(value.properties).to.have.property('mystr', 'value'); + expect(value.properties).to.have.property('mynum', 123); + expect(value.properties).to.have.property('mybool', true); }); }); @@ -29,6 +37,11 @@ describe("settings values", () => { engines: [['google', 'https://google.com/search?q={}']], }, blacklist: ['*.slack.com'], + "properties": { + "mystr": "value", + "mynum": 123, + "mybool": true, + } }; let value = values.valueFromForm(form); @@ -37,6 +50,9 @@ describe("settings values", () => { expect(JSON.stringify(value.search)).to.deep.equal(JSON.stringify({ default: "google", engines: { google: "https://google.com/search?q={}"} })); expect(value.search).to.deep.equal({ default: "google", engines: { google: "https://google.com/search?q={}"} }); expect(value.blacklist).to.deep.equal(["*.slack.com"]); + expect(value.properties).to.have.property('mystr', 'value'); + expect(value.properties).to.have.property('mynum', 123); + expect(value.properties).to.have.property('mybool', true); }); it('convert from empty form', () => { @@ -45,6 +61,7 @@ describe("settings values", () => { expect(value).to.not.have.key('keymaps'); expect(value).to.not.have.key('search'); expect(value).to.not.have.key('blacklist'); + expect(value).to.not.have.key('properties'); }); it('override keymaps', () => { @@ -96,7 +113,12 @@ describe("settings values", () => { 0: { type: 'scroll.home' }, }, search: { default: 'google', engines: { google: 'https://google.com/search?q={}' }}, - blacklist: [ '*.slack.com'] + blacklist: [ '*.slack.com'], + properties: { + "mystr": "value", + "mynum": 123, + "mybool": true, + } }; let allowed = ['scroll.vertically?{"count":1}', 'scroll.home' ]; let form = values.formFromValue(value, allowed); @@ -109,6 +131,9 @@ describe("settings values", () => { expect(form.search).to.have.deep.property('engines', [['google', 'https://google.com/search?q={}']]); expect(form.blacklist).to.have.lengthOf(1); expect(form.blacklist).to.include('*.slack.com'); + expect(form.properties).to.have.property('mystr', 'value'); + expect(form.properties).to.have.property('mynum', 123); + expect(form.properties).to.have.property('mybool', true); }); }); }); -- cgit v1.2.3 From f4499b840c73c8872c69a53ef7a4c2b9dca977a6 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Mon, 8 Jan 2018 13:54:18 +0900 Subject: Set default properties --- src/shared/settings/default.js | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/shared/settings') diff --git a/src/shared/settings/default.js b/src/shared/settings/default.js index d187565..6425354 100644 --- a/src/shared/settings/default.js +++ b/src/shared/settings/default.js @@ -58,6 +58,8 @@ export default { "duckduckgo": "https://duckduckgo.com/?q={}", "twitter": "https://twitter.com/search?q={}", "wikipedia": "https://en.wikipedia.org/w/index.php?search={}" + }, + "properties": { } } }`, -- cgit v1.2.3 From fe48dce1c9b6f003c669cb19542063c8ac0c91ba Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Mon, 8 Jan 2018 16:25:09 +0900 Subject: default property values --- src/settings/components/index.jsx | 4 ++-- src/shared/settings/properties.js | 15 +++++++++++++++ src/shared/settings/property-types.js | 6 ------ src/shared/settings/validator.js | 5 ++--- src/shared/settings/values.js | 6 +++++- 5 files changed, 24 insertions(+), 12 deletions(-) create mode 100644 src/shared/settings/properties.js delete mode 100644 src/shared/settings/property-types.js (limited to 'src/shared/settings') diff --git a/src/settings/components/index.jsx b/src/settings/components/index.jsx index c41aa6b..d7696a1 100644 --- a/src/settings/components/index.jsx +++ b/src/settings/components/index.jsx @@ -5,7 +5,7 @@ import SearchForm from './form/search-form'; import KeymapsForm from './form/keymaps-form'; import BlacklistForm from './form/blacklist-form'; import PropertiesForm from './form/properties-form'; -import PropertyTypes from 'shared/settings/property-types'; +import * as properties from 'shared/settings/properties'; import * as settingActions from 'settings/actions/setting'; import * as validator from 'shared/settings/validator'; import * as settingsValues from 'shared/settings/values'; @@ -70,7 +70,7 @@ class SettingsComponent extends Component {
Properties this.bindForm('properties', value)} /> diff --git a/src/shared/settings/properties.js b/src/shared/settings/properties.js new file mode 100644 index 0000000..ff8039b --- /dev/null +++ b/src/shared/settings/properties.js @@ -0,0 +1,15 @@ +const types = { + // TODO describe property types here + // mystr: 'string', + // mynum: 'number', + // mybool: 'boolean', +}; + +const defaults = { + // TODO describe property defaults values + // mystr: 'hello', + // mynum: 123, + // mybool: true, +}; + +export { types, defaults }; diff --git a/src/shared/settings/property-types.js b/src/shared/settings/property-types.js deleted file mode 100644 index bcfa809..0000000 --- a/src/shared/settings/property-types.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - // TODO describe property types here - // mystr: 'string', - // mynum: 'number', - // mybool: 'boolean', -}; diff --git a/src/shared/settings/validator.js b/src/shared/settings/validator.js index 6fadac7..744f63d 100644 --- a/src/shared/settings/validator.js +++ b/src/shared/settings/validator.js @@ -1,5 +1,4 @@ import operations from 'shared/operations'; -import propertyTypes from './property-types'; const VALID_TOP_KEYS = ['keymaps', 'search', 'blacklist', 'properties']; const VALID_OPERATION_VALUES = Object.keys(operations).map((key) => { @@ -51,10 +50,10 @@ const validateSearch = (search) => { const validateProperties = (properties) => { for (let name of Object.keys(properties)) { - if (!propertyTypes[name]) { + if (!properties.types[name]) { throw new Error(`Unknown property name: "${name}"`); } - if (typeof properties[name] !== propertyTypes[name]) { + if (typeof properties[name] !== properties.types[name]) { throw new Error(`Invalid type for property: "${name}"`); } } diff --git a/src/shared/settings/values.js b/src/shared/settings/values.js index 5027ba5..bd03be2 100644 --- a/src/shared/settings/values.js +++ b/src/shared/settings/values.js @@ -1,3 +1,5 @@ +import * as properties from './properties'; + const operationFromFormName = (name) => { let [type, argStr] = name.split('?'); let args = {}; @@ -81,11 +83,13 @@ const formFromValue = (value, allowedOps) => { } } + let formProperties = Object.assign({}, properties.defaults, value.properties); + return { keymaps, search, blacklist: value.blacklist, - properties: value.properties, + properties: formProperties, }; }; -- cgit v1.2.3 From d23c190cad81ec2109f69b4afa957194d029caaf Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Mon, 8 Jan 2018 21:40:12 +0900 Subject: default properties --- src/shared/settings/validator.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src/shared/settings') diff --git a/src/shared/settings/validator.js b/src/shared/settings/validator.js index 744f63d..1589420 100644 --- a/src/shared/settings/validator.js +++ b/src/shared/settings/validator.js @@ -1,4 +1,5 @@ import operations from 'shared/operations'; +import * as properties from './properties'; const VALID_TOP_KEYS = ['keymaps', 'search', 'blacklist', 'properties']; const VALID_OPERATION_VALUES = Object.keys(operations).map((key) => { @@ -48,12 +49,12 @@ const validateSearch = (search) => { } }; -const validateProperties = (properties) => { - for (let name of Object.keys(properties)) { +const validateProperties = (props) => { + for (let name of Object.keys(props)) { if (!properties.types[name]) { throw new Error(`Unknown property name: "${name}"`); } - if (typeof properties[name] !== properties.types[name]) { + if (typeof props[name] !== properties.types[name]) { throw new Error(`Invalid type for property: "${name}"`); } } -- cgit v1.2.3 From 6083e70ea089fa2683741a1118be0e4e6b76f858 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Mon, 8 Jan 2018 21:54:16 +0900 Subject: separate setting actions and reducers --- src/background/actions/index.js | 4 ++++ src/background/actions/setting.js | 13 +++++++++++++ src/background/components/background.js | 4 ++-- src/background/index.js | 4 ++-- src/background/reducers/index.js | 2 +- src/background/reducers/setting.js | 17 +++++++++++++++++ src/settings/actions/setting.js | 18 +++++++----------- src/shared/settings/storage.js | 31 +++++++++++++++++++++++++++++++ test/background/reducers/setting.test.js | 19 +++++++++++++++++++ 9 files changed, 96 insertions(+), 16 deletions(-) create mode 100644 src/background/actions/index.js create mode 100644 src/background/actions/setting.js create mode 100644 src/background/reducers/setting.js create mode 100644 src/shared/settings/storage.js create mode 100644 test/background/reducers/setting.test.js (limited to 'src/shared/settings') diff --git a/src/background/actions/index.js b/src/background/actions/index.js new file mode 100644 index 0000000..8c212c2 --- /dev/null +++ b/src/background/actions/index.js @@ -0,0 +1,4 @@ +export default { + // Settings + SETTING_SET_SETTINGS: 'setting.set.settings', +}; diff --git a/src/background/actions/setting.js b/src/background/actions/setting.js new file mode 100644 index 0000000..0454a68 --- /dev/null +++ b/src/background/actions/setting.js @@ -0,0 +1,13 @@ +import actions from '../actions'; +import * as settingsStorage from 'shared/settings/storage'; + +const load = () => { + return settingsStorage.loadValue().then((value) => { + return { + type: actions.SETTING_SET_SETTINGS, + value, + }; + }); +}; + +export { load }; diff --git a/src/background/components/background.js b/src/background/components/background.js index 2d94310..22c6693 100644 --- a/src/background/components/background.js +++ b/src/background/components/background.js @@ -1,6 +1,6 @@ import messages from 'shared/messages'; import * as operationActions from 'background/actions/operation'; -import * as settingsActions from 'settings/actions/setting'; +import * as settingActions from 'background/actions/setting'; import * as tabActions from 'background/actions/tab'; import * as commands from 'shared/commands'; @@ -46,7 +46,7 @@ export default class BackgroundComponent { case messages.CONSOLE_QUERY_COMPLETIONS: return commands.complete(message.text, settings.value); case messages.SETTINGS_RELOAD: - this.store.dispatch(settingsActions.load()); + this.store.dispatch(settingActions.load()); return this.broadcastSettingsChanged(); } } diff --git a/src/background/index.js b/src/background/index.js index 8a68767..3ef712f 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -1,4 +1,4 @@ -import * as settingsActions from 'settings/actions/setting'; +import * as settingActions from 'background/actions/setting'; import messages from 'shared/messages'; import BackgroundComponent from 'background/components/background'; import reducers from 'background/reducers'; @@ -16,4 +16,4 @@ const store = createStore(reducers, (e, sender) => { // eslint-disable-next-line no-unused-vars const backgroundComponent = new BackgroundComponent(store); -store.dispatch(settingsActions.load()); +store.dispatch(settingActions.load()); diff --git a/src/background/reducers/index.js b/src/background/reducers/index.js index 4be8fac..dab0c62 100644 --- a/src/background/reducers/index.js +++ b/src/background/reducers/index.js @@ -1,4 +1,4 @@ -import settingReducer from 'settings/reducers/setting'; +import settingReducer from './setting'; // Make setting reducer instead of re-use const defaultState = { diff --git a/src/background/reducers/setting.js b/src/background/reducers/setting.js new file mode 100644 index 0000000..70bf8ea --- /dev/null +++ b/src/background/reducers/setting.js @@ -0,0 +1,17 @@ +import actions from 'settings/actions'; + +const defaultState = { + value: {}, +}; + +export default function reducer(state = defaultState, action = {}) { + switch (action.type) { + case actions.SETTING_SET_SETTINGS: + return { + value: action.value, + }; + default: + return state; + } +} + diff --git a/src/settings/actions/setting.js b/src/settings/actions/setting.js index 1d01fda..92c9f8a 100644 --- a/src/settings/actions/setting.js +++ b/src/settings/actions/setting.js @@ -1,26 +1,22 @@ import actions from 'settings/actions'; import messages from 'shared/messages'; import DefaultSettings from 'shared/settings/default'; +import * as settingsStorage from 'shared/settings/storage'; import * as settingsValues from 'shared/settings/values'; const load = () => { - return browser.storage.local.get('settings').then(({ settings }) => { - if (!settings) { - return set(DefaultSettings); - } - return set(Object.assign({}, DefaultSettings, settings)); - }, console.error); + return settingsStorage.loadRaw().then((settings) => { + return set(settings); + }); }; const save = (settings) => { - return browser.storage.local.set({ - settings, - }).then(() => { + return settingsStorage.save(settings).then(() => { return browser.runtime.sendMessage({ type: messages.SETTINGS_RELOAD - }).then(() => { - return set(settings); }); + }).then(() => { + return set(settings); }); }; diff --git a/src/shared/settings/storage.js b/src/shared/settings/storage.js new file mode 100644 index 0000000..1edb441 --- /dev/null +++ b/src/shared/settings/storage.js @@ -0,0 +1,31 @@ +import DefaultSettings from './default'; +import * as settingsValues from './values'; + +const loadRaw = () => { + return browser.storage.local.get('settings').then(({ settings }) => { + if (!settings) { + return DefaultSettings; + } + return Object.assign({}, DefaultSettings, settings); + }); +}; + +const loadValue = () => { + return loadRaw().then((settings) => { + let value = JSON.parse(DefaultSettings.json); + if (settings.source === 'json') { + value = settingsValues.valueFromJson(settings.json); + } else if (settings.source === 'form') { + value = settingsValues.valueFromForm(settings.form); + } + return value; + }); +}; + +const save = (settings) => { + return browser.storage.local.set({ + settings, + }); +}; + +export { loadRaw, loadValue, save }; diff --git a/test/background/reducers/setting.test.js b/test/background/reducers/setting.test.js new file mode 100644 index 0000000..a6a5573 --- /dev/null +++ b/test/background/reducers/setting.test.js @@ -0,0 +1,19 @@ +import { expect } from "chai"; +import actions from 'background/actions'; +import settingReducer from 'background/reducers/setting'; + +describe("setting reducer", () => { + it('return the initial state', () => { + let state = settingReducer(undefined, {}); + expect(state).to.have.deep.property('value', {}); + }); + + it('return next state for SETTING_SET_SETTINGS', () => { + let action = { + type: actions.SETTING_SET_SETTINGS, + value: { key: 123 }, + }; + let state = settingReducer(undefined, action); + expect(state).to.have.deep.property('value', { key: 123 }); + }); +}); -- cgit v1.2.3