From 85b4bd5b073af729ff325e90fa3c9078f90277ac Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 7 Jul 2018 14:33:14 +0900 Subject: Use pure redux on console --- test/console/actions/console.test.js | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) (limited to 'test') diff --git a/test/console/actions/console.test.js b/test/console/actions/console.test.js index 77855cd..10cd9fe 100644 --- a/test/console/actions/console.test.js +++ b/test/console/actions/console.test.js @@ -23,14 +23,6 @@ describe("console actions", () => { }); }); - describe("showInfo", () => { - it('create CONSOLE_SHOW_INFO action', () => { - let action = consoleActions.showInfo('an info'); - expect(action.type).to.equal(actions.CONSOLE_SHOW_INFO); - expect(action.text).to.equal('an info'); - }); - }); - describe("showError", () => { it('create CONSOLE_SHOW_ERROR action', () => { let action = consoleActions.showError('an error'); @@ -39,6 +31,14 @@ describe("console actions", () => { }); }); + describe("showInfo", () => { + it('create CONSOLE_SHOW_INFO action', () => { + let action = consoleActions.showInfo('an info'); + expect(action.type).to.equal(actions.CONSOLE_SHOW_INFO); + expect(action.text).to.equal('an info'); + }); + }); + describe("hideCommand", () => { it('create CONSOLE_HIDE_COMMAND action', () => { let action = consoleActions.hideCommand(); @@ -54,15 +54,6 @@ describe("console actions", () => { }); }); - describe("setCompletions", () => { - it('create CONSOLE_SET_COMPLETIONS action', () => { - let action = consoleActions.setCompletions('query', [1, 2, 3]); - expect(action.type).to.equal(actions.CONSOLE_SET_COMPLETIONS); - expect(action.completionSource).to.deep.equal('query'); - expect(action.completions).to.deep.equal([1, 2, 3]); - }); - }); - describe("completionPrev", () => { it('create CONSOLE_COMPLETION_PREV action', () => { let action = consoleActions.completionPrev(); -- cgit v1.2.3 From cf0dcf25226611590d76d263b5a4acdc89510d09 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 7 Jul 2018 17:52:01 +0900 Subject: blacklist as an util --- src/content/components/common/index.js | 31 ++++++++----------------- src/shared/blacklists.js | 13 +++++++++++ test/shared/blacklists.test.js | 42 ++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 21 deletions(-) create mode 100644 src/shared/blacklists.js create mode 100644 test/shared/blacklists.test.js (limited to 'test') diff --git a/src/content/components/common/index.js b/src/content/components/common/index.js index 3a23956..e81bf1f 100644 --- a/src/content/components/common/index.js +++ b/src/content/components/common/index.js @@ -5,6 +5,7 @@ import * as settingActions from 'content/actions/setting'; import messages from 'shared/messages'; import * as addonActions from '../../actions/addon'; import * as re from 'shared/utils/re'; +import * as blacklists from 'shared/blacklists'; export default class Common { constructor(win, store) { @@ -46,27 +47,15 @@ export default class Common { }); } - let blacklist = JSON.stringify(this.store.getState().setting.blacklist); - if (blacklist !== this.prevBlacklist) { - this.prevBlacklist = blacklist; - - this.disableIfBlack(this.store.getState().setting.blacklist); - } - } - - disableIfBlack(blacklist) { - let loc = this.win.location; - let partial = loc.host + loc.pathname; - let matched = blacklist - .map((item) => { - let pattern = item.includes('/') ? item : item + '/*'; - return re.fromWildcard(pattern); - }) - .some(regex => regex.test(partial)); - if (matched) { - this.store.dispatch(addonActions.disable()); - } else { - this.store.dispatch(addonActions.enable()); + let blacklist = this.store.getState().setting.blacklist; + let str = JSON.stringify(blacklist) + if (blacklist !== str) { + this.prevBlacklist = str; + if (blacklists.includes(blacklist, this.win.location)) { + this.store.dispatch(addonActions.disable()); + } else { + this.store.dispatch(addonActions.enable()); + } } } diff --git a/src/shared/blacklists.js b/src/shared/blacklists.js new file mode 100644 index 0000000..21e3e14 --- /dev/null +++ b/src/shared/blacklists.js @@ -0,0 +1,13 @@ +import * as re from 'shared/utils/re'; + +const includes = (blacklist, url) => { + let u = new URL(url) + return blacklist.some((item) => { + if (!item.includes('/')) { + return re.fromWildcard(item).test(u.hostname); + } + return re.fromWildcard(item).test(u.hostname + u.pathname); + }); +} + +export { includes }; diff --git a/test/shared/blacklists.test.js b/test/shared/blacklists.test.js new file mode 100644 index 0000000..87e89c5 --- /dev/null +++ b/test/shared/blacklists.test.js @@ -0,0 +1,42 @@ +import { includes } from 'shared/blacklists'; + +describe("shared/blacklist", () => { + it('matches by *', () => { + let blacklist = ['*']; + + expect(includes(blacklist, 'https://github.com/abc')).to.be.true; + }) + + it('matches by hostname', () => { + let blacklist = ['github.com']; + + expect(includes(blacklist, 'https://github.com')).to.be.true; + expect(includes(blacklist, 'https://gist.github.com')).to.be.false; + expect(includes(blacklist, 'https://github.com/ueokande')).to.be.true; + expect(includes(blacklist, 'https://github.org')).to.be.false; + expect(includes(blacklist, 'https://google.com/search?q=github.org')).to.be.false; + }) + + it('matches by hostname with wildcard', () => { + let blacklist = ['*.github.com']; + + expect(includes(blacklist, 'https://github.com')).to.be.false; + expect(includes(blacklist, 'https://gist.github.com')).to.be.true; + }) + + it('matches by path', () => { + let blacklist = ['github.com/abc']; + + expect(includes(blacklist, 'https://github.com/abc')).to.be.true; + expect(includes(blacklist, 'https://github.com/abcdef')).to.be.false; + expect(includes(blacklist, 'https://gist.github.com/abc')).to.be.false; + }) + + it('matches by path with wildcard', () => { + let blacklist = ['github.com/abc*']; + + expect(includes(blacklist, 'https://github.com/abc')).to.be.true; + expect(includes(blacklist, 'https://github.com/abcdef')).to.be.true; + expect(includes(blacklist, 'https://gist.github.com/abc')).to.be.false; + }) +}); -- cgit v1.2.3 From efa1cb396733ac0bedb7c1e86fd1974fbc801135 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 7 Jul 2018 22:05:22 +0900 Subject: Use official redux on content --- src/content/actions/addon.js | 22 +++++++++++-------- src/content/actions/index.js | 4 +--- src/content/actions/operation.js | 4 ++-- src/content/actions/setting.js | 2 +- src/content/components/common/index.js | 35 +++++++----------------------- src/content/components/common/keymapper.js | 4 +++- src/content/index.js | 1 - src/content/reducers/addon.js | 10 ++------- src/shared/blacklists.js | 4 ++-- test/content/actions/addon.test.js | 25 --------------------- test/content/reducers/addon.test.js | 24 ++------------------ 11 files changed, 34 insertions(+), 101 deletions(-) delete mode 100644 test/content/actions/addon.test.js (limited to 'test') diff --git a/src/content/actions/addon.js b/src/content/actions/addon.js index 8d38025..b30cf16 100644 --- a/src/content/actions/addon.js +++ b/src/content/actions/addon.js @@ -1,15 +1,19 @@ +import messages from 'shared/messages'; import actions from 'content/actions'; -const enable = () => { - return { type: actions.ADDON_ENABLE }; -}; +const enable = () => setEnabled(true); -const disable = () => { - return { type: actions.ADDON_DISABLE }; -}; +const disable = () => setEnabled(false); -const toggleEnabled = () => { - return { type: actions.ADDON_TOGGLE_ENABLED }; +const setEnabled = async(enabled) => { + await browser.runtime.sendMessage({ + type: messages.ADDON_ENABLED_RESPONSE, + enabled, + }); + return { + type: actions.ADDON_SET_ENABLED, + enabled, + }; }; -export { enable, disable, toggleEnabled }; +export { enable, disable, setEnabled }; diff --git a/src/content/actions/index.js b/src/content/actions/index.js index 7e32e12..1c51ab0 100644 --- a/src/content/actions/index.js +++ b/src/content/actions/index.js @@ -1,8 +1,6 @@ export default { // Enable/disable - ADDON_ENABLE: 'addon.enable', - ADDON_DISABLE: 'addon.disable', - ADDON_TOGGLE_ENABLED: 'addon.toggle.enabled', + ADDON_SET_ENABLED: 'addon.set.enabled', // Settings SETTING_SET: 'setting.set', diff --git a/src/content/actions/operation.js b/src/content/actions/operation.js index a1761fc..c1bd1c8 100644 --- a/src/content/actions/operation.js +++ b/src/content/actions/operation.js @@ -9,7 +9,7 @@ import * as addonActions from './addon'; import * as properties from 'shared/settings/properties'; // eslint-disable-next-line complexity, max-lines-per-function -const exec = (operation, repeat, settings) => { +const exec = (operation, repeat, settings, addonEnabled) => { let smoothscroll = settings.properties.smoothscroll || properties.defaults.smoothscroll; switch (operation.type) { @@ -18,7 +18,7 @@ const exec = (operation, repeat, settings) => { case operations.ADDON_DISABLE: return addonActions.disable(); case operations.ADDON_TOGGLE_ENABLED: - return addonActions.toggleEnabled(); + return addonActions.setEnabled(!addonEnabled); case operations.FIND_NEXT: window.top.postMessage(JSON.stringify({ type: messages.FIND_NEXT, diff --git a/src/content/actions/setting.js b/src/content/actions/setting.js index 50c09ca..1c15dd7 100644 --- a/src/content/actions/setting.js +++ b/src/content/actions/setting.js @@ -34,4 +34,4 @@ const load = async() => { return set(settings); }; -export { load }; +export { set, load }; diff --git a/src/content/components/common/index.js b/src/content/components/common/index.js index e81bf1f..a1e71a1 100644 --- a/src/content/components/common/index.js +++ b/src/content/components/common/index.js @@ -4,7 +4,6 @@ import FollowComponent from './follow'; import * as settingActions from 'content/actions/setting'; import messages from 'shared/messages'; import * as addonActions from '../../actions/addon'; -import * as re from 'shared/utils/re'; import * as blacklists from 'shared/blacklists'; export default class Common { @@ -24,44 +23,26 @@ export default class Common { this.reloadSettings(); messages.onMessage(this.onMessage.bind(this)); - store.subscribe(() => this.update()); } onMessage(message) { + let { enabled } = this.store.getState().addon; switch (message.type) { case messages.SETTINGS_CHANGED: return this.reloadSettings(); case messages.ADDON_TOGGLE_ENABLED: - return this.store.dispatch(addonActions.toggleEnabled()); - } - } - - update() { - let enabled = this.store.getState().addon.enabled; - if (enabled !== this.prevEnabled) { - this.prevEnabled = enabled; - - browser.runtime.sendMessage({ - type: messages.ADDON_ENABLED_RESPONSE, - enabled, - }); - } - - let blacklist = this.store.getState().setting.blacklist; - let str = JSON.stringify(blacklist) - if (blacklist !== str) { - this.prevBlacklist = str; - if (blacklists.includes(blacklist, this.win.location)) { - this.store.dispatch(addonActions.disable()); - } else { - this.store.dispatch(addonActions.enable()); - } + this.store.dispatch(addonActions.setEnabled(!enabled)); } } reloadSettings() { try { - this.store.dispatch(settingActions.load()); + this.store.dispatch(settingActions.load()).then(({ value: settings }) => { + let enabled = !blacklists.includes( + settings.blacklist, this.win.location.href + ); + this.store.dispatch(addonActions.setEnabled(enabled)); + }); } catch (e) { // Sometime sendMessage fails when background script is not ready. console.warn(e); diff --git a/src/content/components/common/keymapper.js b/src/content/components/common/keymapper.js index 326bdd9..4c294b4 100644 --- a/src/content/components/common/keymapper.js +++ b/src/content/components/common/keymapper.js @@ -48,7 +48,9 @@ export default class KeymapperComponent { return true; } let operation = keymaps.get(matched[0]); - let act = operationActions.exec(operation, key.repeat, state.setting); + let act = operationActions.exec( + operation, key.repeat, state.setting, state.addon.enabled + ); this.store.dispatch(act); this.store.dispatch(inputActions.clearKeys()); return true; diff --git a/src/content/index.js b/src/content/index.js index d6743ce..3b0b49b 100644 --- a/src/content/index.js +++ b/src/content/index.js @@ -10,7 +10,6 @@ const store = createStore( applyMiddleware(promise), ); - if (window.self === window.top) { new TopContentComponent(window, store); // eslint-disable-line no-new } else { diff --git a/src/content/reducers/addon.js b/src/content/reducers/addon.js index b881ca0..0def55a 100644 --- a/src/content/reducers/addon.js +++ b/src/content/reducers/addon.js @@ -6,15 +6,9 @@ const defaultState = { export default function reducer(state = defaultState, action = {}) { switch (action.type) { - case actions.ADDON_ENABLE: + case actions.ADDON_SET_ENABLED: return { ...state, - enabled: true, }; - case actions.ADDON_DISABLE: - return { ...state, - enabled: false, }; - case actions.ADDON_TOGGLE_ENABLED: - return { ...state, - enabled: !state.enabled, }; + enabled: action.enabled, }; default: return state; } diff --git a/src/shared/blacklists.js b/src/shared/blacklists.js index 21e3e14..19ed3f1 100644 --- a/src/shared/blacklists.js +++ b/src/shared/blacklists.js @@ -1,13 +1,13 @@ import * as re from 'shared/utils/re'; const includes = (blacklist, url) => { - let u = new URL(url) + let u = new URL(url); return blacklist.some((item) => { if (!item.includes('/')) { return re.fromWildcard(item).test(u.hostname); } return re.fromWildcard(item).test(u.hostname + u.pathname); }); -} +}; export { includes }; diff --git a/test/content/actions/addon.test.js b/test/content/actions/addon.test.js deleted file mode 100644 index 5f96372..0000000 --- a/test/content/actions/addon.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import actions from 'content/actions'; -import * as addonActions from 'content/actions/addon'; - -describe("addon actions", () => { - describe("enable", () => { - it('create ADDON_ENABLE action', () => { - let action = addonActions.enable(); - expect(action.type).to.equal(actions.ADDON_ENABLE); - }); - }); - - describe("disable", () => { - it('create ADDON_DISABLE action', () => { - let action = addonActions.disable(); - expect(action.type).to.equal(actions.ADDON_DISABLE); - }); - }); - - describe("toggle", () => { - it('create ADDON_TOGGLE_ENABLED action', () => { - let action = addonActions.toggleEnabled(); - expect(action.type).to.equal(actions.ADDON_TOGGLE_ENABLED); - }); - }); -}); diff --git a/test/content/reducers/addon.test.js b/test/content/reducers/addon.test.js index 8c546d2..d4eb845 100644 --- a/test/content/reducers/addon.test.js +++ b/test/content/reducers/addon.test.js @@ -7,31 +7,11 @@ describe("addon reducer", () => { expect(state).to.have.property('enabled', true); }); - it('return next state for ADDON_ENABLE', () => { - let action = { type: actions.ADDON_ENABLE}; + it('return next state for ADDON_SET_ENABLED', () => { + let action = { type: actions.ADDON_SET_ENABLED, enabled: true }; let prev = { enabled: false }; let state = addonReducer(prev, action); expect(state.enabled).is.equal(true); }); - - it('return next state for ADDON_DISABLE', () => { - let action = { type: actions.ADDON_DISABLE}; - let prev = { enabled: true }; - let state = addonReducer(prev, action); - - expect(state.enabled).is.equal(false); - }); - - it('return next state for ADDON_TOGGLE_ENABLED', () => { - let action = { type: actions.ADDON_TOGGLE_ENABLED }; - let state = { enabled: false }; - - state = addonReducer(state, action); - expect(state.enabled).is.equal(true); - - state = addonReducer(state, action); - expect(state.enabled).is.equal(false); - }); - }); -- cgit v1.2.3 From 96649fef63f467d45aefee93dfb599546151c21d Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sun, 8 Jul 2018 11:39:56 +0900 Subject: See you my redux --- src/shared/store/index.js | 53 ------------------- src/shared/store/provider.jsx | 15 ------ test/shared/store/index.test.js | 110 ---------------------------------------- 3 files changed, 178 deletions(-) delete mode 100644 src/shared/store/index.js delete mode 100644 src/shared/store/provider.jsx delete mode 100644 test/shared/store/index.test.js (limited to 'test') diff --git a/src/shared/store/index.js b/src/shared/store/index.js deleted file mode 100644 index 2fafdf1..0000000 --- a/src/shared/store/index.js +++ /dev/null @@ -1,53 +0,0 @@ -class Store { - constructor(reducer, catcher) { - this.reducer = reducer; - this.catcher = catcher; - this.subscribers = []; - try { - this.state = this.reducer(undefined, {}); - } catch (e) { - catcher(e); - } - } - - dispatch(action, sender) { - if (action instanceof Promise) { - action.then((a) => { - this.transitNext(a, sender); - }).catch((e) => { - this.catcher(e, sender); - }); - } else { - try { - this.transitNext(action, sender); - } catch (e) { - this.catcher(e, sender); - } - } - return action; - } - - getState() { - return this.state; - } - - subscribe(callback) { - this.subscribers.push(callback); - } - - transitNext(action, sender) { - let newState = this.reducer(this.state, action); - if (JSON.stringify(this.state) !== JSON.stringify(newState)) { - this.state = newState; - this.subscribers.forEach(f => f(sender)); - } - } -} - -const empty = () => {}; - -const createStore = (reducer, catcher = empty) => { - return new Store(reducer, catcher); -}; - -export { createStore }; diff --git a/src/shared/store/provider.jsx b/src/shared/store/provider.jsx deleted file mode 100644 index fe925aa..0000000 --- a/src/shared/store/provider.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import { h, Component } from 'preact'; - -class Provider extends Component { - getChildContext() { - return { store: this.props.store }; - } - - render() { - return
- { this.props.children } -
; - } -} - -export default Provider; diff --git a/test/shared/store/index.test.js b/test/shared/store/index.test.js deleted file mode 100644 index 5b69b40..0000000 --- a/test/shared/store/index.test.js +++ /dev/null @@ -1,110 +0,0 @@ -import { createStore } from 'shared/store'; - -describe("Store class", () => { - const reducer = (state, action) => { - if (state == undefined) { - return 0; - } - return state + action; - }; - - describe("#dispatch", () => { - it('transit status by immediate action', () => { - let store = createStore(reducer); - store.dispatch(10); - expect(store.getState()).to.equal(10); - - store.dispatch(-20); - expect(store.getState()).to.equal(-10); - }); - - it('returns next state by immediate action', () => { - let store = createStore(reducer); - let dispatchedAction = store.dispatch(11); - expect(dispatchedAction).to.equal(11); - }); - - it('transit status by Promise action', () => { - let store = createStore(reducer); - let p1 = Promise.resolve(10); - - return store.dispatch(p1).then(() => { - expect(store.getState()).to.equal(10); - }).then(() => { - store.dispatch(Promise.resolve(-20)); - }).then(() => { - expect(store.getState()).to.equal(-10); - }); - }); - - it('returns next state by promise action', () => { - let store = createStore(reducer); - let dispatchedAction = store.dispatch(Promise.resolve(11)); - return dispatchedAction.then((value) => { - expect(value).to.equal(11); - }); - }); - }); - - describe("#subscribe", () => { - it('invoke callback', (done) => { - let store = createStore(reducer); - store.subscribe(() => { - expect(store.getState()).to.equal(15); - done(); - }); - store.dispatch(15); - }); - - it('propagate sender object', (done) => { - let store = createStore(reducer); - store.subscribe((sender) => { - expect(sender).to.equal('sender'); - done(); - }); - store.dispatch(15, 'sender'); - }); - }) - - describe("catcher", () => { - it('catch an error in reducer on initializing by immediate action', (done) => { - let store = createStore(() => { - throw new Error(); - }, (e) => { - expect(e).to.be.an('error'); - done(); - }); - }); - - it('catch an error in reducer on initializing by immediate action', (done) => { - let store = createStore((state, action) => { - if (state === undefined) return 0; - throw new Error(); - }, (e) => { - expect(e).to.be.an('error'); - done(); - }); - store.dispatch(20); - }); - - it('catch an error in reducer on initializing by promise action', (done) => { - let store = createStore((state, action) => { - if (state === undefined) return 0; - throw new Error(); - }, (e) => { - expect(e).to.be.an('error'); - done(); - }); - store.dispatch(Promise.resolve(20)); - }); - - it('catch an error in promise action', (done) => { - let store = createStore((state, action) => 0, (e) => { - expect(e).to.be.an('error'); - done(); - }); - store.dispatch(new Promise(() => { throw new Error() })); - }); - }) -}); - -- cgit v1.2.3 From df890379ca3e41bc20a80fca98a4c3363aecffa9 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sun, 8 Jul 2018 17:25:34 +0900 Subject: Clean setting --- src/settings/actions/index.js | 3 + src/settings/actions/setting.js | 51 ++++++++-- src/settings/components/index.jsx | 168 +++++++++------------------------ src/settings/reducers/setting.js | 23 ++++- test/settings/reducers/setting.test.js | 42 ++++++++- 5 files changed, 144 insertions(+), 143 deletions(-) (limited to 'test') diff --git a/src/settings/actions/index.js b/src/settings/actions/index.js index 8c212c2..016f2a5 100644 --- a/src/settings/actions/index.js +++ b/src/settings/actions/index.js @@ -1,4 +1,7 @@ export default { // Settings SETTING_SET_SETTINGS: 'setting.set.settings', + SETTING_SHOW_ERROR: 'setting.show.error', + SETTING_SWITCH_TO_FORM: 'setting.switch.to.form', + SETTING_SWITCH_TO_JSON: 'setting.switch.to.json', }; diff --git a/src/settings/actions/setting.js b/src/settings/actions/setting.js index 1219ba5..3bb24be 100644 --- a/src/settings/actions/setting.js +++ b/src/settings/actions/setting.js @@ -1,8 +1,9 @@ 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 validator from 'shared/settings/validator'; +import KeymapsForm from '../components/form/keymaps-form'; import * as settingsValues from 'shared/settings/values'; +import * as settingsStorage from 'shared/settings/storage'; const load = async() => { let settings = await settingsStorage.loadRaw(); @@ -10,6 +11,18 @@ const load = async() => { }; const save = async(settings) => { + try { + if (settings.source === 'json') { + let value = JSON.parse(settings.json); + validator.validate(value); + } + } catch (e) { + return { + type: actions.SETTING_SHOW_ERROR, + error: e.toString(), + json: settings.json, + }; + } await settingsStorage.save(settings); await browser.runtime.sendMessage({ type: messages.SETTINGS_RELOAD @@ -17,21 +30,39 @@ const save = async(settings) => { return set(settings); }; -const set = (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); +const switchToForm = (json) => { + try { + validator.validate(JSON.parse(json)); + // AllowdOps filters operations, this is dirty dependency + let form = settingsValues.formFromJson(json, KeymapsForm.AllowdOps); + return { + type: actions.SETTING_SWITCH_TO_FORM, + form, + }; + } catch (e) { + return { + type: actions.SETTING_SHOW_ERROR, + error: e.toString(), + json, + }; } +}; +const switchToJson = (form) => { + let json = settingsValues.jsonFromForm(form); + return { + type: actions.SETTING_SWITCH_TO_JSON, + json, + }; +}; + +const set = (settings) => { return { type: actions.SETTING_SET_SETTINGS, source: settings.source, json: settings.json, form: settings.form, - value, }; }; -export { load, save }; +export { load, save, switchToForm, switchToJson }; diff --git a/src/settings/components/index.jsx b/src/settings/components/index.jsx index c479986..66dc940 100644 --- a/src/settings/components/index.jsx +++ b/src/settings/components/index.jsx @@ -1,5 +1,6 @@ import './site.scss'; import { h, Component } from 'preact'; +import { connect } from 'preact-redux'; import Input from './ui/input'; import SearchForm from './form/search-form'; import KeymapsForm from './form/keymaps-form'; @@ -7,63 +8,36 @@ import BlacklistForm from './form/blacklist-form'; import PropertiesForm from './form/properties-form'; 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'; const DO_YOU_WANT_TO_CONTINUE = 'Some settings in JSON can be lost when migrating. ' + 'Do you want to continue?'; class SettingsComponent extends Component { - constructor(props, context) { - super(props, context); - - this.state = { - settings: { - json: '', - }, - errors: { - json: '', - } - }; - this.context.store.subscribe(this.stateChanged.bind(this)); - } - componentDidMount() { - this.context.store.dispatch(settingActions.load()); - } - - stateChanged() { - let settings = this.context.store.getState(); - this.setState({ - settings: { - source: settings.source, - json: settings.json, - form: settings.form, - } - }); + this.props.dispatch(settingActions.load()); } - renderFormFields() { + renderFormFields(form) { return
Keybindings this.bindForm('keymaps', value)} />
Search Engines this.bindForm('search', value)} />
Blacklist this.bindForm('blacklist', value)} />
@@ -71,33 +45,33 @@ class SettingsComponent extends Component { Properties this.bindForm('properties', value)} />
; } - renderJsonFields() { + renderJsonFields(json, error) { return
; } render() { let fields = null; - if (this.state.settings.source === 'form') { - fields = this.renderFormFields(); - } else if (this.state.settings.source === 'json') { - fields = this.renderJsonFields(); + if (this.props.source === 'form') { + fields = this.renderFormFields(this.props.form); + } else if (this.props.source === 'json') { + fields = this.renderJsonFields(this.props.json, this.props.error); } return (
@@ -108,7 +82,7 @@ class SettingsComponent extends Component { id='setting-source-form' name='source' label='Use form' - checked={this.state.settings.source === 'form'} + checked={this.props.source === 'form'} value='form' onChange={this.bindSource.bind(this)} /> @@ -116,7 +90,7 @@ class SettingsComponent extends Component { type='radio' name='source' label='Use plain JSON' - checked={this.state.settings.source === 'json'} + checked={this.props.source === 'json'} value='json' onChange={this.bindSource.bind(this)} /> @@ -126,98 +100,44 @@ class SettingsComponent extends Component { ); } - validate(target) { - if (target.name === 'json') { - let settings = JSON.parse(target.value); - validator.validate(settings); - } - } - - validateValue(e) { - let next = { ...this.state }; - - next.errors.json = ''; - try { - this.validate(e.target); - } catch (err) { - next.errors.json = err.message; - } - next.settings[e.target.name] = e.target.value; - } - bindForm(name, value) { - let next = { ...this.state, - settings: { ...this.state.settings, - form: { ...this.state.settings.form }}}; - next.settings.form[name] = value; - this.setState(next); - this.context.store.dispatch(settingActions.save(next.settings)); - } - - bindValue(e) { - let next = { ...this.state }; - let error = false; - - next.errors.json = ''; - try { - this.validate(e.target); - } catch (err) { - next.errors.json = err.message; - error = true; - } - next.settings[e.target.name] = e.target.value; - - this.setState(this.state); - if (!error) { - this.context.store.dispatch(settingActions.save(next.settings)); - } - } - - migrateToForm() { - let b = window.confirm(DO_YOU_WANT_TO_CONTINUE); - if (!b) { - this.setState(this.state); - return; - } - try { - validator.validate(JSON.parse(this.state.settings.json)); - } catch (err) { - this.setState(this.state); - return; - } - - let form = settingsValues.formFromJson( - this.state.settings.json, KeymapsForm.AllowdOps); - let next = { ...this.state }; - next.settings.form = form; - next.settings.source = 'form'; - next.errors.json = ''; - - this.setState(next); - this.context.store.dispatch(settingActions.save(next.settings)); + let settings = { + source: this.props.source, + json: this.props.json, + form: { ...this.props.form }, + }; + settings.form[name] = value; + this.props.dispatch(settingActions.save(settings)); } - migrateToJson() { - let json = settingsValues.jsonFromForm(this.state.settings.form); - let next = { ...this.state }; - next.settings.json = json; - next.settings.source = 'json'; - next.errors.json = ''; - - this.setState(next); - this.context.store.dispatch(settingActions.save(next.settings)); + bindJson(e) { + let settings = { + source: this.props.source, + json: e.target.value, + form: this.props.form, + }; + this.props.dispatch(settingActions.save(settings)); } bindSource(e) { - let from = this.state.settings.source; + let from = this.props.source; let to = e.target.value; if (from === 'form' && to === 'json') { - this.migrateToJson(); + this.props.dispatch(settingActions.switchToJson(this.props.form)); } else if (from === 'json' && to === 'form') { - this.migrateToForm(); + let b = window.confirm(DO_YOU_WANT_TO_CONTINUE); + if (!b) { + return; + } + this.props.dispatch(settingActions.switchToForm(this.props.json)); } + + let settings = this.context.store.getState(); + this.props.dispatch(settingActions.save(settings)); } } -export default SettingsComponent; +const mapStateToProps = state => state; + +export default connect(mapStateToProps)(SettingsComponent); diff --git a/src/settings/reducers/setting.js b/src/settings/reducers/setting.js index 70c6183..8e4a415 100644 --- a/src/settings/reducers/setting.js +++ b/src/settings/reducers/setting.js @@ -4,20 +4,33 @@ const defaultState = { source: '', json: '', form: null, - value: {} + error: '', }; export default function reducer(state = defaultState, action = {}) { switch (action.type) { case actions.SETTING_SET_SETTINGS: - return { + return { ...state, source: action.source, json: action.json, form: action.form, - value: action.value, - }; + errors: '', + error: '', }; + case actions.SETTING_SHOW_ERROR: + return { ...state, + error: action.text, + json: action.json, }; + case actions.SETTING_SWITCH_TO_FORM: + return { ...state, + error: '', + source: 'form', + form: action.form, }; + case actions.SETTING_SWITCH_TO_JSON: + return { ...state, + error: '', + source: 'json', + json: action.json, }; default: return state; } } - diff --git a/test/settings/reducers/setting.test.js b/test/settings/reducers/setting.test.js index b9579cf..d800394 100644 --- a/test/settings/reducers/setting.test.js +++ b/test/settings/reducers/setting.test.js @@ -1,21 +1,55 @@ import actions from 'settings/actions'; import settingReducer from 'settings/reducers/setting'; -describe("setting reducer", () => { +describe("settings setting reducer", () => { it('return the initial state', () => { let state = settingReducer(undefined, {}); expect(state).to.have.deep.property('json', ''); - expect(state).to.have.deep.property('value', {}); + expect(state).to.have.deep.property('form', null); + expect(state).to.have.deep.property('error', ''); }); it('return next state for SETTING_SET_SETTINGS', () => { let action = { type: actions.SETTING_SET_SETTINGS, + source: 'json', json: '{ "key": "value" }', - value: { key: 123 }, + form: {}, }; let state = settingReducer(undefined, action); + expect(state).to.have.deep.property('source', 'json'); expect(state).to.have.deep.property('json', '{ "key": "value" }'); - expect(state).to.have.deep.property('value', { key: 123 }); + expect(state).to.have.deep.property('form', {}); + }); + + it('return next state for SETTING_SHOW_ERROR', () => { + let action = { + type: actions.SETTING_SHOW_ERROR, + text: 'bad value', + json: '{}', + }; + let state = settingReducer(undefined, action); + expect(state).to.have.deep.property('error', 'bad value'); + expect(state).to.have.deep.property('json', '{}'); + }); + + it('return next state for SETTING_SWITCH_TO_FORM', () => { + let action = { + type: actions.SETTING_SWITCH_TO_FORM, + form: {}, + }; + let state = settingReducer(undefined, action); + expect(state).to.have.deep.property('form', {}); + expect(state).to.have.deep.property('source', 'form'); + }); + + it('return next state for SETTING_SWITCH_TO_JSON', () => { + let action = { + type: actions.SETTING_SWITCH_TO_JSON, + json: '{}', + }; + let state = settingReducer(undefined, action); + expect(state).to.have.deep.property('json', '{}'); + expect(state).to.have.deep.property('source', 'json'); }); }); -- cgit v1.2.3 From 067da88d06fbffca323ecdbaf8b1011f88225219 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Wed, 11 Jul 2018 21:01:48 +0900 Subject: Move versions to background --- src/background/index.js | 2 +- src/background/shared/versions/index.js | 38 +++++++++++++++++++++++ src/background/shared/versions/release-notes.js | 8 +++++ src/background/shared/versions/storage.js | 10 +++++++ src/shared/versions/index.js | 38 ----------------------- src/shared/versions/release-notes.js | 8 ----- src/shared/versions/storage.js | 10 ------- test/background/shared/versions/index.test.js | 40 +++++++++++++++++++++++++ test/background/shared/versions/storage.test.js | 28 +++++++++++++++++ test/shared/versions/index.test.js | 40 ------------------------- test/shared/versions/storage.test.js | 28 ----------------- 11 files changed, 125 insertions(+), 125 deletions(-) create mode 100644 src/background/shared/versions/index.js create mode 100644 src/background/shared/versions/release-notes.js create mode 100644 src/background/shared/versions/storage.js delete mode 100644 src/shared/versions/index.js delete mode 100644 src/shared/versions/release-notes.js delete mode 100644 src/shared/versions/storage.js create mode 100644 test/background/shared/versions/index.test.js create mode 100644 test/background/shared/versions/storage.test.js delete mode 100644 test/shared/versions/index.test.js delete mode 100644 test/shared/versions/storage.test.js (limited to 'test') diff --git a/src/background/index.js b/src/background/index.js index 8c4eafc..1e4c078 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -6,7 +6,7 @@ import IndicatorComponent from 'background/components/indicator'; import reducers from 'background/reducers'; import { createStore, applyMiddleware } from 'redux'; import promise from 'redux-promise'; -import * as versions from 'shared/versions'; +import * as versions from './shared/versions'; const store = createStore( reducers, diff --git a/src/background/shared/versions/index.js b/src/background/shared/versions/index.js new file mode 100644 index 0000000..aa09c92 --- /dev/null +++ b/src/background/shared/versions/index.js @@ -0,0 +1,38 @@ +import * as storage from './storage'; +import * as releaseNotes from './release-notes'; +import manifest from '../../../../manifest.json'; + +const NOTIFICATION_ID = 'vimvixen-update'; + +const notificationClickListener = (id) => { + if (id !== NOTIFICATION_ID) { + return; + } + + browser.tabs.create({ url: releaseNotes.url(manifest.version) }); + browser.notifications.onClicked.removeListener(notificationClickListener); +}; + +const checkUpdated = async() => { + let prev = await storage.load(); + if (!prev) { + return true; + } + return manifest.version !== prev; +}; + +const notify = () => { + browser.notifications.onClicked.addListener(notificationClickListener); + return browser.notifications.create(NOTIFICATION_ID, { + 'type': 'basic', + 'iconUrl': browser.extension.getURL('resources/icon_48x48.png'), + 'title': 'Vim Vixen ' + manifest.version + ' has been installed', + 'message': 'Click here to see release notes', + }); +}; + +const commit = () => { + storage.save(manifest.version); +}; + +export { checkUpdated, notify, commit }; diff --git a/src/background/shared/versions/release-notes.js b/src/background/shared/versions/release-notes.js new file mode 100644 index 0000000..6ef2335 --- /dev/null +++ b/src/background/shared/versions/release-notes.js @@ -0,0 +1,8 @@ +const url = (version) => { + if (version) { + return 'https://github.com/ueokande/vim-vixen/releases/tag/' + version; + } + return 'https://github.com/ueokande/vim-vixen/releases/'; +}; + +export { url }; diff --git a/src/background/shared/versions/storage.js b/src/background/shared/versions/storage.js new file mode 100644 index 0000000..7883258 --- /dev/null +++ b/src/background/shared/versions/storage.js @@ -0,0 +1,10 @@ +const load = async() => { + let { version } = await browser.storage.local.get('version'); + return version; +}; + +const save = (version) => { + return browser.storage.local.set({ version }); +}; + +export { load, save }; diff --git a/src/shared/versions/index.js b/src/shared/versions/index.js deleted file mode 100644 index ba3d183..0000000 --- a/src/shared/versions/index.js +++ /dev/null @@ -1,38 +0,0 @@ -import * as storage from './storage'; -import * as releaseNotes from './release-notes'; -import manifest from '../../../manifest.json'; - -const NOTIFICATION_ID = 'vimvixen-update'; - -const notificationClickListener = (id) => { - if (id !== NOTIFICATION_ID) { - return; - } - - browser.tabs.create({ url: releaseNotes.url(manifest.version) }); - browser.notifications.onClicked.removeListener(notificationClickListener); -}; - -const checkUpdated = async() => { - let prev = await storage.load(); - if (!prev) { - return true; - } - return manifest.version !== prev; -}; - -const notify = () => { - browser.notifications.onClicked.addListener(notificationClickListener); - return browser.notifications.create(NOTIFICATION_ID, { - 'type': 'basic', - 'iconUrl': browser.extension.getURL('resources/icon_48x48.png'), - 'title': 'Vim Vixen ' + manifest.version + ' has been installed', - 'message': 'Click here to see release notes', - }); -}; - -const commit = () => { - storage.save(manifest.version); -}; - -export { checkUpdated, notify, commit }; diff --git a/src/shared/versions/release-notes.js b/src/shared/versions/release-notes.js deleted file mode 100644 index 6ef2335..0000000 --- a/src/shared/versions/release-notes.js +++ /dev/null @@ -1,8 +0,0 @@ -const url = (version) => { - if (version) { - return 'https://github.com/ueokande/vim-vixen/releases/tag/' + version; - } - return 'https://github.com/ueokande/vim-vixen/releases/'; -}; - -export { url }; diff --git a/src/shared/versions/storage.js b/src/shared/versions/storage.js deleted file mode 100644 index 7883258..0000000 --- a/src/shared/versions/storage.js +++ /dev/null @@ -1,10 +0,0 @@ -const load = async() => { - let { version } = await browser.storage.local.get('version'); - return version; -}; - -const save = (version) => { - return browser.storage.local.set({ version }); -}; - -export { load, save }; diff --git a/test/background/shared/versions/index.test.js b/test/background/shared/versions/index.test.js new file mode 100644 index 0000000..d65dd9a --- /dev/null +++ b/test/background/shared/versions/index.test.js @@ -0,0 +1,40 @@ +import * as versions from 'background/shared/versions'; +import manifest from '../../../../manifest.json'; + +describe("shared/versions/storage", () => { + describe('#checkUpdated', () => { + beforeEach(() => { + return browser.storage.local.remove('version'); + }); + + it('return true if no previous versions', async() => { + let updated = await versions.checkUpdated(); + expect(updated).to.be.true; + }); + + it('return true if updated', async() => { + await browser.storage.local.set({ version: '0.001' }); + let updated = await versions.checkUpdated(); + expect(updated).to.be.true; + }); + + it('return false if not updated', async() => { + await browser.storage.local.set({ version: manifest.version }); + let updated = await versions.checkUpdated(); + expect(updated).to.be.false; + }); + }); + + describe('#commit', () => { + beforeEach(() => { + return browser.storage.local.remove('version'); + }); + + it('saves current version from manifest.json', async() => { + await versions.commit(); + let { version } = await browser.storage.local.get('version'); + expect(version).to.be.a('string'); + expect(version).to.equal(manifest.version); + }); + }); +}); diff --git a/test/background/shared/versions/storage.test.js b/test/background/shared/versions/storage.test.js new file mode 100644 index 0000000..f452516 --- /dev/null +++ b/test/background/shared/versions/storage.test.js @@ -0,0 +1,28 @@ +import * as storage from 'background/shared/versions/storage'; + +describe("shared/versions/storage", () => { + describe('#load', () => { + beforeEach(() => { + return browser.storage.local.remove('version'); + }); + + it('loads saved version', async() => { + await browser.storage.local.set({ version: '1.2.3' }); + let version = await storage.load(); + expect(version).to.equal('1.2.3'); + }); + + it('returns undefined if no versions in storage', async() => { + let version = await storage.load(); + expect(version).to.be.a('undefined'); + }); + }); + + describe('#save', () => { + it('saves version string', async() => { + await storage.save('2.3.4'); + let { version } = await browser.storage.local.get('version'); + expect(version).to.equal('2.3.4'); + }); + }); +}); diff --git a/test/shared/versions/index.test.js b/test/shared/versions/index.test.js deleted file mode 100644 index d90f04c..0000000 --- a/test/shared/versions/index.test.js +++ /dev/null @@ -1,40 +0,0 @@ -import * as versions from 'shared/versions'; -import manifest from '../../../manifest.json'; - -describe("shared/versions/storage", () => { - describe('#checkUpdated', () => { - beforeEach(() => { - return browser.storage.local.remove('version'); - }); - - it('return true if no previous versions', async() => { - let updated = await versions.checkUpdated(); - expect(updated).to.be.true; - }); - - it('return true if updated', async() => { - await browser.storage.local.set({ version: '0.001' }); - let updated = await versions.checkUpdated(); - expect(updated).to.be.true; - }); - - it('return false if not updated', async() => { - await browser.storage.local.set({ version: manifest.version }); - let updated = await versions.checkUpdated(); - expect(updated).to.be.false; - }); - }); - - describe('#commit', () => { - beforeEach(() => { - return browser.storage.local.remove('version'); - }); - - it('saves current version from manifest.json', async() => { - await versions.commit(); - let { version } = await browser.storage.local.get('version'); - expect(version).to.be.a('string'); - expect(version).to.equal(manifest.version); - }); - }); -}); diff --git a/test/shared/versions/storage.test.js b/test/shared/versions/storage.test.js deleted file mode 100644 index f541abf..0000000 --- a/test/shared/versions/storage.test.js +++ /dev/null @@ -1,28 +0,0 @@ -import * as storage from 'shared/versions/storage'; - -describe("shared/versions/storage", () => { - describe('#load', () => { - beforeEach(() => { - return browser.storage.local.remove('version'); - }); - - it('loads saved version', async() => { - await browser.storage.local.set({ version: '1.2.3' }); - let version = await storage.load(); - expect(version).to.equal('1.2.3'); - }); - - it('returns undefined if no versions in storage', async() => { - let version = await storage.load(); - expect(version).to.be.a('undefined'); - }); - }); - - describe('#save', () => { - it('saves version string', async() => { - await storage.save('2.3.4'); - let { version } = await browser.storage.local.get('version'); - expect(version).to.equal('2.3.4'); - }); - }); -}); -- cgit v1.2.3