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/settings/components/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/settings') diff --git a/src/settings/components/index.jsx b/src/settings/components/index.jsx index 73520ca..074c4c7 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 * as settingActions from 'settings/actions/setting'; -import * as validator from 'shared/validators/setting'; +import * as validator from 'shared/settings/validator'; import * as settingsValues from 'shared/settings/values'; const DO_YOU_WANT_TO_CONTINUE = -- cgit v1.2.3 From 86df54067f8a105264a6816e115fd65efa75fb5b Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Fri, 5 Jan 2018 22:54:36 +0900 Subject: Add PropertiesForm --- src/settings/components/form/properties-form.jsx | 60 +++++++++++++++ src/settings/components/form/properties-form.scss | 12 +++ src/settings/components/index.jsx | 10 +++ .../components/form/properties-form.test.jsx | 86 ++++++++++++++++++++++ 4 files changed, 168 insertions(+) create mode 100644 src/settings/components/form/properties-form.jsx create mode 100644 src/settings/components/form/properties-form.scss create mode 100644 test/settings/components/form/properties-form.test.jsx (limited to 'src/settings') diff --git a/src/settings/components/form/properties-form.jsx b/src/settings/components/form/properties-form.jsx new file mode 100644 index 0000000..55c8512 --- /dev/null +++ b/src/settings/components/form/properties-form.jsx @@ -0,0 +1,60 @@ +import './properties-form.scss'; +import { h, Component } from 'preact'; + +class PropertiesForm extends Component { + + render() { + let types = this.props.types; + let value = this.props.value; + if (!value) { + value = {}; + } + + return
+ { + Object.keys(types).map((name) => { + let type = types[name]; + let inputType = null; + if (type === 'string') { + inputType = 'text'; + } else if (type === 'number') { + inputType = 'number'; + } else if (type === 'boolean') { + inputType = 'checkbox'; + } + return
+ +
; + }) + } +
; + } + + bindValue(e) { + if (!this.props.onChange) { + return; + } + + let name = e.target.name; + let next = Object.assign({}, this.props.value); + if (e.target.type.toLowerCase() === 'checkbox') { + next[name] = e.target.checked; + } else if (e.target.type.toLowerCase() === 'number') { + next[name] = Number(e.target.value); + } else { + next[name] = e.target.value; + } + + this.props.onChange(next); + } +} + +export default PropertiesForm; diff --git a/src/settings/components/form/properties-form.scss b/src/settings/components/form/properties-form.scss new file mode 100644 index 0000000..7c9e167 --- /dev/null +++ b/src/settings/components/form/properties-form.scss @@ -0,0 +1,12 @@ +.form-properties-form { + &-row { + .column-name { + display: inline-block; + min-width: 5rem; + font-weight: bold; + } + .column-input { + line-height: 2.2rem; + } + } +} diff --git a/src/settings/components/index.jsx b/src/settings/components/index.jsx index 074c4c7..c41aa6b 100644 --- a/src/settings/components/index.jsx +++ b/src/settings/components/index.jsx @@ -4,6 +4,8 @@ import Input from './ui/input'; 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 settingActions from 'settings/actions/setting'; import * as validator from 'shared/settings/validator'; import * as settingsValues from 'shared/settings/values'; @@ -65,6 +67,14 @@ class SettingsComponent extends Component { onChange={value => this.bindForm('blacklist', value)} /> +
+ Properties + this.bindForm('properties', value)} + /> +
; } diff --git a/test/settings/components/form/properties-form.test.jsx b/test/settings/components/form/properties-form.test.jsx new file mode 100644 index 0000000..4807361 --- /dev/null +++ b/test/settings/components/form/properties-form.test.jsx @@ -0,0 +1,86 @@ +import { expect } from 'chai'; +import { h, render } from 'preact'; +import PropertiesForm from 'settings/components/form/properties-form' + +describe("settings/form/PropertiesForm", () => { + beforeEach(() => { + document.body.innerHTML = ''; + }); + + describe('render', () => { + it('renders PropertiesForm', () => { + let types = { + mystr: 'string', + mynum: 'number', + mybool: 'boolean', + empty: 'string', + } + let value = { + mystr: 'abc', + mynum: 123, + mybool: true, + }; + render(, document.body); + + let strInput = document.querySelector('input[name=mystr]'); + let numInput = document.querySelector('input[name=mynum]'); + let boolInput = document.querySelector('input[name=mybool]'); + let emptyInput = document.querySelector('input[name=empty]'); + + expect(strInput.type).to.equals('text'); + expect(strInput.value).to.equal('abc'); + expect(numInput.type).to.equals('number'); + expect(numInput.value).to.equal('123'); + expect(boolInput.type).to.equals('checkbox'); + expect(boolInput.checked).to.be.true; + expect(emptyInput.type).to.equals('text'); + expect(emptyInput.value).to.be.empty; + }); + }); + + describe('onChange', () => { + it('invokes onChange event on text changed', (done) => { + render( { + expect(value).to.have.property('myvalue', 'abcd'); + done(); + }} + />, document.body); + + let input = document.querySelector('input[name=myvalue]'); + input.value = 'abcd' + input.dispatchEvent(new Event('change')) + }); + + it('invokes onChange event on number changeed', (done) => { + render( { + expect(value).to.have.property('myvalue', 1234); + done(); + }} + />, document.body); + + let input = document.querySelector('input[name=myvalue]'); + input.value = '1234' + input.dispatchEvent(new Event('change')) + }); + + it('invokes onChange event on checkbox changed', (done) => { + render( { + expect(value).to.have.property('myvalue', true); + done(); + }} + />, document.body); + + let input = document.querySelector('input[name=myvalue]'); + input.click(); + }); + }); +}); -- 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/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 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/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