commit
944dea5919
44 changed files with 658 additions and 675 deletions
@ -0,0 +1,41 @@ |
|||||||
|
import messages from 'shared/messages'; |
||||||
|
|
||||||
|
const error = async(tab, text) => { |
||||||
|
await browser.tabs.sendMessage(tab.id, { |
||||||
|
type: messages.CONSOLE_SHOW_ERROR, |
||||||
|
text, |
||||||
|
}); |
||||||
|
return { type: '' }; |
||||||
|
}; |
||||||
|
|
||||||
|
const info = async(tab, text) => { |
||||||
|
await browser.tabs.sendMessage(tab.id, { |
||||||
|
type: messages.CONSOLE_SHOW_INFO, |
||||||
|
text, |
||||||
|
}); |
||||||
|
return { type: '' }; |
||||||
|
}; |
||||||
|
|
||||||
|
const showCommand = async(tab, command) => { |
||||||
|
await browser.tabs.sendMessage(tab.id, { |
||||||
|
type: messages.CONSOLE_SHOW_COMMAND, |
||||||
|
command, |
||||||
|
}); |
||||||
|
return { type: '' }; |
||||||
|
}; |
||||||
|
|
||||||
|
const showFind = async(tab) => { |
||||||
|
await browser.tabs.sendMessage(tab.id, { |
||||||
|
type: messages.CONSOLE_SHOW_FIND |
||||||
|
}); |
||||||
|
return { type: '' }; |
||||||
|
}; |
||||||
|
|
||||||
|
const hide = async(tab) => { |
||||||
|
await browser.tabs.sendMessage(tab.id, { |
||||||
|
type: messages.CONSOLE_HIDE, |
||||||
|
}); |
||||||
|
return { type: '' }; |
||||||
|
}; |
||||||
|
|
||||||
|
export { error, info, showCommand, showFind, hide }; |
@ -1,17 +1,8 @@ |
|||||||
import settingReducer from './setting'; |
import { combineReducers } from 'redux'; |
||||||
import findReducer from './find'; |
import setting from './setting'; |
||||||
import tabReducer from './tab'; |
import find from './find'; |
||||||
|
import tab from './tab'; |
||||||
|
|
||||||
// Make setting reducer instead of re-use
|
export default combineReducers({ |
||||||
const defaultState = { |
setting, find, tab, |
||||||
setting: settingReducer(undefined, {}), |
}); |
||||||
find: findReducer(undefined, {}), |
|
||||||
tab: tabReducer(undefined, {}), |
|
||||||
}; |
|
||||||
|
|
||||||
export default function reducer(state = defaultState, action = {}) { |
|
||||||
return { ...state, |
|
||||||
setting: settingReducer(state.setting, action), |
|
||||||
find: findReducer(state.find, action), |
|
||||||
tab: tabReducer(state.tab, action), }; |
|
||||||
} |
|
||||||
|
@ -1,6 +1,6 @@ |
|||||||
import * as storage from './storage'; |
import * as storage from './storage'; |
||||||
import * as releaseNotes from './release-notes'; |
import * as releaseNotes from './release-notes'; |
||||||
import manifest from '../../../manifest.json'; |
import manifest from '../../../../manifest.json'; |
||||||
|
|
||||||
const NOTIFICATION_ID = 'vimvixen-update'; |
const NOTIFICATION_ID = 'vimvixen-update'; |
||||||
|
|
@ -1,15 +1,19 @@ |
|||||||
|
import messages from 'shared/messages'; |
||||||
import actions from 'content/actions'; |
import actions from 'content/actions'; |
||||||
|
|
||||||
const enable = () => { |
const enable = () => setEnabled(true); |
||||||
return { type: actions.ADDON_ENABLE }; |
|
||||||
}; |
|
||||||
|
|
||||||
const disable = () => { |
const disable = () => setEnabled(false); |
||||||
return { type: actions.ADDON_DISABLE }; |
|
||||||
}; |
|
||||||
|
|
||||||
const toggleEnabled = () => { |
const setEnabled = async(enabled) => { |
||||||
return { type: actions.ADDON_TOGGLE_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 }; |
||||||
|
@ -1,25 +1,10 @@ |
|||||||
import addonReducer from './addon'; |
import { combineReducers } from 'redux'; |
||||||
import findReducer from './find'; |
import addon from './addon'; |
||||||
import settingReducer from './setting'; |
import find from './find'; |
||||||
import inputReducer from './input'; |
import setting from './setting'; |
||||||
import followControllerReducer from './follow-controller'; |
import input from './input'; |
||||||
|
import followController from './follow-controller'; |
||||||
|
|
||||||
// Make setting reducer instead of re-use
|
export default combineReducers({ |
||||||
const defaultState = { |
addon, find, setting, input, followController, |
||||||
addon: addonReducer(undefined, {}), |
}); |
||||||
find: findReducer(undefined, {}), |
|
||||||
setting: settingReducer(undefined, {}), |
|
||||||
input: inputReducer(undefined, {}), |
|
||||||
followController: followControllerReducer(undefined, {}), |
|
||||||
}; |
|
||||||
|
|
||||||
export default function reducer(state = defaultState, action = {}) { |
|
||||||
return { |
|
||||||
...state, |
|
||||||
addon: addonReducer(state.addon, action), |
|
||||||
find: findReducer(state.find, action), |
|
||||||
setting: settingReducer(state.setting, action), |
|
||||||
input: inputReducer(state.input, action), |
|
||||||
followController: followControllerReducer(state.followController, action), |
|
||||||
}; |
|
||||||
} |
|
||||||
|
@ -1,4 +1,7 @@ |
|||||||
export default { |
export default { |
||||||
// Settings
|
// Settings
|
||||||
SETTING_SET_SETTINGS: 'setting.set.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', |
||||||
}; |
}; |
||||||
|
@ -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 }; |
@ -0,0 +1,11 @@ |
|||||||
|
export default { |
||||||
|
set: 'Set a value of the property', |
||||||
|
open: 'Open a URL or search by keywords in current tab', |
||||||
|
tabopen: 'Open a URL or search by keywords in new tab', |
||||||
|
winopen: 'Open a URL or search by keywords in new window', |
||||||
|
buffer: 'Sekect tabs by matched keywords', |
||||||
|
bdelete: 'Close a certain tab matched by keywords', |
||||||
|
bdeletes: 'Close all tabs matched by keywords', |
||||||
|
quit: 'Close the current tab', |
||||||
|
quitall: 'Close all tabs', |
||||||
|
}; |
@ -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 }; |
|
@ -1,15 +0,0 @@ |
|||||||
import { h, Component } from 'preact'; |
|
||||||
|
|
||||||
class Provider extends Component { |
|
||||||
getChildContext() { |
|
||||||
return { store: this.props.store }; |
|
||||||
} |
|
||||||
|
|
||||||
render() { |
|
||||||
return <div> |
|
||||||
{ this.props.children } |
|
||||||
</div>; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
export default Provider; |
|
@ -1,5 +1,5 @@ |
|||||||
import * as versions from 'shared/versions'; |
import * as versions from 'background/shared/versions'; |
||||||
import manifest from '../../../manifest.json'; |
import manifest from '../../../../manifest.json'; |
||||||
|
|
||||||
describe("shared/versions/storage", () => { |
describe("shared/versions/storage", () => { |
||||||
describe('#checkUpdated', () => { |
describe('#checkUpdated', () => { |
@ -1,4 +1,4 @@ |
|||||||
import * as storage from 'shared/versions/storage'; |
import * as storage from 'background/shared/versions/storage'; |
||||||
|
|
||||||
describe("shared/versions/storage", () => { |
describe("shared/versions/storage", () => { |
||||||
describe('#load', () => { |
describe('#load', () => { |
@ -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); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
@ -1,21 +1,55 @@ |
|||||||
import actions from 'settings/actions'; |
import actions from 'settings/actions'; |
||||||
import settingReducer from 'settings/reducers/setting'; |
import settingReducer from 'settings/reducers/setting'; |
||||||
|
|
||||||
describe("setting reducer", () => { |
describe("settings setting reducer", () => { |
||||||
it('return the initial state', () => { |
it('return the initial state', () => { |
||||||
let state = settingReducer(undefined, {}); |
let state = settingReducer(undefined, {}); |
||||||
expect(state).to.have.deep.property('json', ''); |
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', () => { |
it('return next state for SETTING_SET_SETTINGS', () => { |
||||||
let action = { |
let action = { |
||||||
type: actions.SETTING_SET_SETTINGS, |
type: actions.SETTING_SET_SETTINGS, |
||||||
|
source: 'json', |
||||||
json: '{ "key": "value" }', |
json: '{ "key": "value" }', |
||||||
value: { key: 123 }, |
form: {}, |
||||||
}; |
}; |
||||||
let state = settingReducer(undefined, action); |
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('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'); |
||||||
}); |
}); |
||||||
}); |
}); |
||||||
|
@ -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; |
||||||
|
}) |
||||||
|
}); |
@ -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() })); |
|
||||||
}); |
|
||||||
}) |
|
||||||
}); |
|
||||||
|
|
Reference in new issue