Make settings as a clean architecture
This commit is contained in:
parent
e76ca380f7
commit
bacf83a320
16 changed files with 223 additions and 196 deletions
|
@ -1,13 +1,9 @@
|
||||||
import Redux from 'redux';
|
import Redux from 'redux';
|
||||||
import Settings from '../../shared/Settings';
|
|
||||||
import * as keyUtils from '../../shared/utils/keys';
|
import * as keyUtils from '../../shared/utils/keys';
|
||||||
|
|
||||||
// Find
|
// Find
|
||||||
export const FIND_SET_KEYWORD = 'find.set.keyword';
|
export const FIND_SET_KEYWORD = 'find.set.keyword';
|
||||||
|
|
||||||
// Settings
|
|
||||||
export const SETTING_SET = 'setting.set';
|
|
||||||
|
|
||||||
// User input
|
// User input
|
||||||
export const INPUT_KEY_PRESS = 'input.key.press';
|
export const INPUT_KEY_PRESS = 'input.key.press';
|
||||||
export const INPUT_CLEAR_KEYS = 'input.clear.keys';
|
export const INPUT_CLEAR_KEYS = 'input.clear.keys';
|
||||||
|
@ -37,11 +33,6 @@ export interface FindSetKeywordAction extends Redux.Action {
|
||||||
found: boolean;
|
found: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SettingSetAction extends Redux.Action {
|
|
||||||
type: typeof SETTING_SET;
|
|
||||||
settings: Settings,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface InputKeyPressAction extends Redux.Action {
|
export interface InputKeyPressAction extends Redux.Action {
|
||||||
type: typeof INPUT_KEY_PRESS;
|
type: typeof INPUT_KEY_PRESS;
|
||||||
key: keyUtils.Key;
|
key: keyUtils.Key;
|
||||||
|
@ -94,7 +85,6 @@ export interface NoopAction extends Redux.Action {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FindAction = FindSetKeywordAction | NoopAction;
|
export type FindAction = FindSetKeywordAction | NoopAction;
|
||||||
export type SettingAction = SettingSetAction;
|
|
||||||
export type InputAction = InputKeyPressAction | InputClearKeysAction;
|
export type InputAction = InputKeyPressAction | InputClearKeysAction;
|
||||||
export type FollowAction =
|
export type FollowAction =
|
||||||
FollowControllerEnableAction | FollowControllerDisableAction |
|
FollowControllerEnableAction | FollowControllerDisableAction |
|
||||||
|
@ -105,7 +95,6 @@ export type MarkAction =
|
||||||
|
|
||||||
export type Action =
|
export type Action =
|
||||||
FindAction |
|
FindAction |
|
||||||
SettingAction |
|
|
||||||
InputAction |
|
InputAction |
|
||||||
FollowAction |
|
FollowAction |
|
||||||
MarkAction |
|
MarkAction |
|
||||||
|
|
|
@ -9,14 +9,16 @@ import * as consoleFrames from '../console-frames';
|
||||||
import * as markActions from './mark';
|
import * as markActions from './mark';
|
||||||
|
|
||||||
import AddonEnabledUseCase from '../usecases/AddonEnabledUseCase';
|
import AddonEnabledUseCase from '../usecases/AddonEnabledUseCase';
|
||||||
|
import { SettingRepositoryImpl } from '../repositories/SettingRepository';
|
||||||
|
|
||||||
let addonEnabledUseCase = new AddonEnabledUseCase();
|
let addonEnabledUseCase = new AddonEnabledUseCase();
|
||||||
|
let settingRepository = new SettingRepositoryImpl();
|
||||||
|
|
||||||
// eslint-disable-next-line complexity, max-lines-per-function
|
// eslint-disable-next-line complexity, max-lines-per-function
|
||||||
const exec = async(
|
const exec = async(
|
||||||
operation: operations.Operation,
|
operation: operations.Operation,
|
||||||
settings: any,
|
|
||||||
): Promise<actions.Action> => {
|
): Promise<actions.Action> => {
|
||||||
|
let settings = settingRepository.get();
|
||||||
let smoothscroll = settings.properties.smoothscroll;
|
let smoothscroll = settings.properties.smoothscroll;
|
||||||
switch (operation.type) {
|
switch (operation.type) {
|
||||||
case operations.ADDON_ENABLE:
|
case operations.ADDON_ENABLE:
|
||||||
|
@ -97,7 +99,8 @@ const exec = async(
|
||||||
break;
|
break;
|
||||||
case operations.URLS_PASTE:
|
case operations.URLS_PASTE:
|
||||||
urls.paste(
|
urls.paste(
|
||||||
window, operation.newTab ? operation.newTab : false, settings.search
|
window, operation.newTab ? operation.newTab : false,
|
||||||
|
settings.search,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
import * as actions from './index';
|
|
||||||
import * as operations from '../../shared/operations';
|
|
||||||
import * as messages from '../../shared/messages';
|
|
||||||
import Settings, { Keymaps } from '../../shared/Settings';
|
|
||||||
|
|
||||||
const reservedKeymaps: Keymaps = {
|
|
||||||
'<Esc>': { type: operations.CANCEL },
|
|
||||||
'<C-[>': { type: operations.CANCEL },
|
|
||||||
};
|
|
||||||
|
|
||||||
const set = (settings: Settings): actions.SettingAction => {
|
|
||||||
return {
|
|
||||||
type: actions.SETTING_SET,
|
|
||||||
settings: {
|
|
||||||
...settings,
|
|
||||||
keymaps: { ...settings.keymaps, ...reservedKeymaps },
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const load = async(): Promise<actions.SettingAction> => {
|
|
||||||
let settings = await browser.runtime.sendMessage({
|
|
||||||
type: messages.SETTINGS_QUERY,
|
|
||||||
});
|
|
||||||
return set(settings);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { set, load };
|
|
17
src/content/client/SettingClient.ts
Normal file
17
src/content/client/SettingClient.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import Settings from '../../shared/Settings';
|
||||||
|
import * as messages from '../../shared/messages';
|
||||||
|
|
||||||
|
export default interface SettingClient {
|
||||||
|
load(): Promise<Settings>;
|
||||||
|
|
||||||
|
// eslint-disable-next-line semi
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SettingClientImpl {
|
||||||
|
async load(): Promise<Settings> {
|
||||||
|
let settings = await browser.runtime.sendMessage({
|
||||||
|
type: messages.SETTINGS_QUERY,
|
||||||
|
});
|
||||||
|
return settings as Settings;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,22 +2,18 @@ import InputComponent from './input';
|
||||||
import FollowComponent from './follow';
|
import FollowComponent from './follow';
|
||||||
import MarkComponent from './mark';
|
import MarkComponent from './mark';
|
||||||
import KeymapperComponent from './keymapper';
|
import KeymapperComponent from './keymapper';
|
||||||
import * as settingActions from '../../actions/setting';
|
|
||||||
import * as messages from '../../../shared/messages';
|
import * as messages from '../../../shared/messages';
|
||||||
import MessageListener from '../../MessageListener';
|
import MessageListener from '../../MessageListener';
|
||||||
import * as blacklists from '../../../shared/blacklists';
|
import * as blacklists from '../../../shared/blacklists';
|
||||||
import * as keys from '../../../shared/utils/keys';
|
import * as keys from '../../../shared/utils/keys';
|
||||||
import * as actions from '../../actions';
|
|
||||||
|
|
||||||
import AddonEnabledUseCase from '../../usecases/AddonEnabledUseCase';
|
import AddonEnabledUseCase from '../../usecases/AddonEnabledUseCase';
|
||||||
|
import SettingUseCase from '../../usecases/SettingUseCase';
|
||||||
|
|
||||||
let addonEnabledUseCase = new AddonEnabledUseCase();
|
let addonEnabledUseCase = new AddonEnabledUseCase();
|
||||||
|
let settingUseCase = new SettingUseCase();
|
||||||
|
|
||||||
export default class Common {
|
export default class Common {
|
||||||
private win: Window;
|
|
||||||
|
|
||||||
private store: any;
|
|
||||||
|
|
||||||
constructor(win: Window, store: any) {
|
constructor(win: Window, store: any) {
|
||||||
const input = new InputComponent(win.document.body);
|
const input = new InputComponent(win.document.body);
|
||||||
const follow = new FollowComponent(win);
|
const follow = new FollowComponent(win);
|
||||||
|
@ -28,9 +24,6 @@ export default class Common {
|
||||||
input.onKey((key: keys.Key) => mark.key(key));
|
input.onKey((key: keys.Key) => mark.key(key));
|
||||||
input.onKey((key: keys.Key) => keymapper.key(key));
|
input.onKey((key: keys.Key) => keymapper.key(key));
|
||||||
|
|
||||||
this.win = win;
|
|
||||||
this.store = store;
|
|
||||||
|
|
||||||
this.reloadSettings();
|
this.reloadSettings();
|
||||||
|
|
||||||
new MessageListener().onBackgroundMessage(this.onMessage.bind(this));
|
new MessageListener().onBackgroundMessage(this.onMessage.bind(this));
|
||||||
|
@ -41,23 +34,22 @@ export default class Common {
|
||||||
case messages.SETTINGS_CHANGED:
|
case messages.SETTINGS_CHANGED:
|
||||||
return this.reloadSettings();
|
return this.reloadSettings();
|
||||||
case messages.ADDON_TOGGLE_ENABLED:
|
case messages.ADDON_TOGGLE_ENABLED:
|
||||||
addonEnabledUseCase.toggle();
|
return addonEnabledUseCase.toggle();
|
||||||
}
|
}
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadSettings() {
|
async reloadSettings() {
|
||||||
try {
|
try {
|
||||||
this.store.dispatch(settingActions.load())
|
let current = await settingUseCase.reload();
|
||||||
.then((action: actions.SettingAction) => {
|
let disabled = blacklists.includes(
|
||||||
let enabled = !blacklists.includes(
|
current.blacklist, window.location.href,
|
||||||
action.settings.blacklist, this.win.location.href
|
);
|
||||||
);
|
if (disabled) {
|
||||||
if (enabled) {
|
addonEnabledUseCase.disable();
|
||||||
addonEnabledUseCase.enable();
|
} else {
|
||||||
} else {
|
addonEnabledUseCase.enable();
|
||||||
addonEnabledUseCase.disable();
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Sometime sendMessage fails when background script is not ready.
|
// Sometime sendMessage fails when background script is not ready.
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
|
|
|
@ -4,8 +4,18 @@ import * as operations from '../../../shared/operations';
|
||||||
import * as keyUtils from '../../../shared/utils/keys';
|
import * as keyUtils from '../../../shared/utils/keys';
|
||||||
|
|
||||||
import AddonEnabledUseCase from '../../usecases/AddonEnabledUseCase';
|
import AddonEnabledUseCase from '../../usecases/AddonEnabledUseCase';
|
||||||
|
import { SettingRepositoryImpl } from '../../repositories/SettingRepository';
|
||||||
|
import { Keymaps } from '../../../shared/Settings';
|
||||||
|
|
||||||
|
type KeymapEntityMap = Map<keyUtils.Key[], operations.Operation>;
|
||||||
|
|
||||||
let addonEnabledUseCase = new AddonEnabledUseCase();
|
let addonEnabledUseCase = new AddonEnabledUseCase();
|
||||||
|
let settingRepository = new SettingRepositoryImpl();
|
||||||
|
|
||||||
|
const reservedKeymaps: Keymaps = {
|
||||||
|
'<Esc>': { type: operations.CANCEL },
|
||||||
|
'<C-[>': { type: operations.CANCEL },
|
||||||
|
};
|
||||||
|
|
||||||
const mapStartsWith = (
|
const mapStartsWith = (
|
||||||
mapping: keyUtils.Key[],
|
mapping: keyUtils.Key[],
|
||||||
|
@ -29,18 +39,11 @@ export default class KeymapperComponent {
|
||||||
this.store = store;
|
this.store = store;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line max-statements
|
|
||||||
key(key: keyUtils.Key): boolean {
|
key(key: keyUtils.Key): boolean {
|
||||||
this.store.dispatch(inputActions.keyPress(key));
|
this.store.dispatch(inputActions.keyPress(key));
|
||||||
|
|
||||||
let state = this.store.getState();
|
let input = this.store.getState().input;
|
||||||
let input = state.input;
|
let keymaps = this.keymapEntityMap();
|
||||||
let keymaps = new Map<keyUtils.Key[], operations.Operation>(
|
|
||||||
state.setting.keymaps.map(
|
|
||||||
(e: {key: keyUtils.Key[], op: operations.Operation}) => [e.key, e.op],
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
let matched = Array.from(keymaps.keys()).filter(
|
let matched = Array.from(keymaps.keys()).filter(
|
||||||
(mapping: keyUtils.Key[]) => {
|
(mapping: keyUtils.Key[]) => {
|
||||||
return mapStartsWith(mapping, input.keys);
|
return mapStartsWith(mapping, input.keys);
|
||||||
|
@ -62,11 +65,23 @@ export default class KeymapperComponent {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
let operation = keymaps.get(matched[0]) as operations.Operation;
|
let operation = keymaps.get(matched[0]) as operations.Operation;
|
||||||
let act = operationActions.exec(
|
let act = operationActions.exec(operation);
|
||||||
operation, state.setting,
|
|
||||||
);
|
|
||||||
this.store.dispatch(act);
|
this.store.dispatch(act);
|
||||||
this.store.dispatch(inputActions.clearKeys());
|
this.store.dispatch(inputActions.clearKeys());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private keymapEntityMap(): KeymapEntityMap {
|
||||||
|
let keymaps = {
|
||||||
|
...settingRepository.get().keymaps,
|
||||||
|
...reservedKeymaps,
|
||||||
|
};
|
||||||
|
let entries = Object.entries(keymaps).map((entry) => {
|
||||||
|
return [
|
||||||
|
keyUtils.fromMapKeys(entry[0]),
|
||||||
|
entry[1],
|
||||||
|
];
|
||||||
|
}) as [keyUtils.Key[], operations.Operation][];
|
||||||
|
return new Map<keyUtils.Key[], operations.Operation>(entries);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,10 @@ import * as consoleFrames from '../..//console-frames';
|
||||||
import * as keyUtils from '../../../shared/utils/keys';
|
import * as keyUtils from '../../../shared/utils/keys';
|
||||||
import Mark from '../../Mark';
|
import Mark from '../../Mark';
|
||||||
|
|
||||||
|
import { SettingRepositoryImpl } from '../../repositories/SettingRepository';
|
||||||
|
|
||||||
|
let settingRepository = new SettingRepositoryImpl();
|
||||||
|
|
||||||
const cancelKey = (key: keyUtils.Key): boolean => {
|
const cancelKey = (key: keyUtils.Key): boolean => {
|
||||||
return key.key === 'Esc' || key.key === '[' && Boolean(key.ctrlKey);
|
return key.key === 'Esc' || key.key === '[' && Boolean(key.ctrlKey);
|
||||||
};
|
};
|
||||||
|
@ -21,8 +25,8 @@ export default class MarkComponent {
|
||||||
|
|
||||||
// eslint-disable-next-line max-statements
|
// eslint-disable-next-line max-statements
|
||||||
key(key: keyUtils.Key) {
|
key(key: keyUtils.Key) {
|
||||||
let { mark: markState, setting } = this.store.getState();
|
let smoothscroll = settingRepository.get().properties.smoothscroll;
|
||||||
let smoothscroll = setting.properties.smoothscroll;
|
let { mark: markState } = this.store.getState();
|
||||||
|
|
||||||
if (!markState.setMode && !markState.jumpMode) {
|
if (!markState.setMode && !markState.jumpMode) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -3,6 +3,10 @@ import * as messages from '../../../shared/messages';
|
||||||
import MessageListener, { WebMessageSender } from '../../MessageListener';
|
import MessageListener, { WebMessageSender } from '../../MessageListener';
|
||||||
import HintKeyProducer from '../../hint-key-producer';
|
import HintKeyProducer from '../../hint-key-producer';
|
||||||
|
|
||||||
|
import { SettingRepositoryImpl } from '../../repositories/SettingRepository';
|
||||||
|
|
||||||
|
let settingRepository = new SettingRepositoryImpl();
|
||||||
|
|
||||||
const broadcastMessage = (win: Window, message: messages.Message): void => {
|
const broadcastMessage = (win: Window, message: messages.Message): void => {
|
||||||
let json = JSON.stringify(message);
|
let json = JSON.stringify(message);
|
||||||
let frames = [win.self].concat(Array.from(win.frames as any));
|
let frames = [win.self].concat(Array.from(win.frames as any));
|
||||||
|
@ -160,7 +164,7 @@ export default class FollowController {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
hintchars() {
|
private hintchars() {
|
||||||
return this.store.getState().setting.properties.hintchars;
|
return settingRepository.get().properties.hintchars;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { combineReducers } from 'redux';
|
import { combineReducers } from 'redux';
|
||||||
import find, { State as FindState } from './find';
|
import find, { State as FindState } from './find';
|
||||||
import setting, { State as SettingState } from './setting';
|
|
||||||
import input, { State as InputState } from './input';
|
import input, { State as InputState } from './input';
|
||||||
import followController, { State as FollowControllerState }
|
import followController, { State as FollowControllerState }
|
||||||
from './follow-controller';
|
from './follow-controller';
|
||||||
|
@ -8,12 +7,11 @@ import mark, { State as MarkState } from './mark';
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
find: FindState;
|
find: FindState;
|
||||||
setting: SettingState;
|
|
||||||
input: InputState;
|
input: InputState;
|
||||||
followController: FollowControllerState;
|
followController: FollowControllerState;
|
||||||
mark: MarkState;
|
mark: MarkState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
find, setting, input, followController, mark,
|
find, input, followController, mark,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
import * as actions from '../actions';
|
|
||||||
import * as keyUtils from '../../shared/utils/keys';
|
|
||||||
import * as operations from '../../shared/operations';
|
|
||||||
import { Search, Properties, DefaultSetting } from '../../shared/Settings';
|
|
||||||
|
|
||||||
export interface State {
|
|
||||||
keymaps: { key: keyUtils.Key[], op: operations.Operation }[];
|
|
||||||
search: Search;
|
|
||||||
properties: Properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
// defaultState does not refer due to the state is load from
|
|
||||||
// background on load.
|
|
||||||
const defaultState: State = {
|
|
||||||
keymaps: [],
|
|
||||||
search: DefaultSetting.search,
|
|
||||||
properties: DefaultSetting.properties,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function reducer(
|
|
||||||
state: State = defaultState,
|
|
||||||
action: actions.SettingAction,
|
|
||||||
): State {
|
|
||||||
switch (action.type) {
|
|
||||||
case actions.SETTING_SET:
|
|
||||||
return {
|
|
||||||
keymaps: Object.entries(action.settings.keymaps).map((entry) => {
|
|
||||||
return {
|
|
||||||
key: keyUtils.fromMapKeys(entry[0]),
|
|
||||||
op: entry[1],
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
properties: action.settings.properties,
|
|
||||||
search: action.settings.search,
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
22
src/content/repositories/SettingRepository.ts
Normal file
22
src/content/repositories/SettingRepository.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import Settings, { DefaultSetting } from '../../shared/Settings';
|
||||||
|
|
||||||
|
let current: Settings = DefaultSetting;
|
||||||
|
|
||||||
|
export default interface SettingRepository {
|
||||||
|
set(setting: Settings): void;
|
||||||
|
|
||||||
|
get(): Settings;
|
||||||
|
|
||||||
|
// eslint-disable-next-line semi
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SettingRepositoryImpl implements SettingRepository {
|
||||||
|
set(setting: Settings): void {
|
||||||
|
current = setting;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(): Settings {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
24
src/content/usecases/SettingUseCase.ts
Normal file
24
src/content/usecases/SettingUseCase.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import SettingRepository, { SettingRepositoryImpl }
|
||||||
|
from '../repositories/SettingRepository';
|
||||||
|
import SettingClient, { SettingClientImpl } from '../client/SettingClient';
|
||||||
|
import Settings from '../../shared/Settings';
|
||||||
|
|
||||||
|
export default class SettingUseCase {
|
||||||
|
private repository: SettingRepository;
|
||||||
|
|
||||||
|
private client: SettingClient;
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
repository = new SettingRepositoryImpl(),
|
||||||
|
client = new SettingClientImpl(),
|
||||||
|
} = {}) {
|
||||||
|
this.repository = repository;
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
async reload(): Promise<Settings> {
|
||||||
|
let settings = await this.client.load();
|
||||||
|
this.repository.set(settings);
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,43 +0,0 @@
|
||||||
import * as actions from 'content/actions';
|
|
||||||
import * as settingActions from 'content/actions/setting';
|
|
||||||
|
|
||||||
describe("setting actions", () => {
|
|
||||||
describe("set", () => {
|
|
||||||
it('create SETTING_SET action', () => {
|
|
||||||
let action = settingActions.set({
|
|
||||||
keymaps: {
|
|
||||||
'dd': 'remove current tab',
|
|
||||||
'z<C-A>': 'increment',
|
|
||||||
},
|
|
||||||
search: {
|
|
||||||
default: "google",
|
|
||||||
engines: {
|
|
||||||
google: 'https://google.com/search?q={}',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
properties: {
|
|
||||||
hintchars: 'abcd1234',
|
|
||||||
},
|
|
||||||
blacklist: [],
|
|
||||||
});
|
|
||||||
expect(action.type).to.equal(actions.SETTING_SET);
|
|
||||||
expect(action.settings.properties.hintchars).to.equal('abcd1234');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('overrides cancel keys', () => {
|
|
||||||
let action = settingActions.set({
|
|
||||||
keymaps: {
|
|
||||||
"k": { "type": "scroll.vertically", "count": -1 },
|
|
||||||
"j": { "type": "scroll.vertically", "count": 1 },
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let keymaps = action.settings.keymaps;
|
|
||||||
expect(action.settings.keymaps).to.deep.equals({
|
|
||||||
"k": { type: "scroll.vertically", count: -1 },
|
|
||||||
"j": { type: "scroll.vertically", count: 1 },
|
|
||||||
'<Esc>': { type: 'cancel' },
|
|
||||||
'<C-[>': { type: 'cancel' },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,31 +0,0 @@
|
||||||
import * as actions from 'content/actions';
|
|
||||||
import settingReducer from 'content/reducers/setting';
|
|
||||||
|
|
||||||
describe("content setting reducer", () => {
|
|
||||||
it('return the initial state', () => {
|
|
||||||
let state = settingReducer(undefined, {});
|
|
||||||
expect(state.keymaps).to.be.empty;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('return next state for SETTING_SET', () => {
|
|
||||||
let newSettings = { red: 'apple', yellow: 'banana' };
|
|
||||||
let action = {
|
|
||||||
type: actions.SETTING_SET,
|
|
||||||
settings: {
|
|
||||||
keymaps: {
|
|
||||||
"zz": { type: "zoom.neutral" },
|
|
||||||
"<S-Esc>": { "type": "addon.toggle.enabled" }
|
|
||||||
},
|
|
||||||
"blacklist": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let state = settingReducer(undefined, action);
|
|
||||||
expect(state.keymaps).to.have.deep.all.members([
|
|
||||||
{ key: [{ key: 'z', shiftKey: false, ctrlKey: false, altKey: false, metaKey: false },
|
|
||||||
{ key: 'z', shiftKey: false, ctrlKey: false, altKey: false, metaKey: false }],
|
|
||||||
op: { type: 'zoom.neutral' }},
|
|
||||||
{ key: [{ key: 'Esc', shiftKey: true, ctrlKey: false, altKey: false, metaKey: false }],
|
|
||||||
op: { type: 'addon.toggle.enabled' }},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
30
test/content/repositories/SettingRepository.test.ts
Normal file
30
test/content/repositories/SettingRepository.test.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { SettingRepositoryImpl } from '../../../src/content/repositories/SettingRepository';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
|
||||||
|
describe('SettingRepositoryImpl', () => {
|
||||||
|
it('updates and gets current value', () => {
|
||||||
|
let sut = new SettingRepositoryImpl();
|
||||||
|
|
||||||
|
let settings = {
|
||||||
|
keymaps: {},
|
||||||
|
search: {
|
||||||
|
default: 'google',
|
||||||
|
engines: {
|
||||||
|
google: 'https://google.com/?q={}',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
hintchars: 'abcd1234',
|
||||||
|
smoothscroll: false,
|
||||||
|
complete: 'sbh',
|
||||||
|
},
|
||||||
|
blacklist: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
sut.set(settings);
|
||||||
|
|
||||||
|
let actual = sut.get();
|
||||||
|
expect(actual.properties.hintchars).to.equal('abcd1234');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
71
test/content/usecases/SettingUseCaase.test.ts
Normal file
71
test/content/usecases/SettingUseCaase.test.ts
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import SettingRepository from '../../../src/content/repositories/SettingRepository';
|
||||||
|
import SettingClient from '../../../src/content/client/SettingClient';
|
||||||
|
import SettingUseCase from '../../../src/content/usecases/SettingUseCase';
|
||||||
|
import Settings, { DefaultSetting } from '../../../src/shared/Settings';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
|
||||||
|
class MockSettingRepository implements SettingRepository {
|
||||||
|
private current: Settings;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.current = DefaultSetting;
|
||||||
|
}
|
||||||
|
|
||||||
|
set(settings: Settings): void {
|
||||||
|
this.current= settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(): Settings {
|
||||||
|
return this.current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockSettingClient implements SettingClient {
|
||||||
|
private data: Settings;
|
||||||
|
|
||||||
|
constructor(data: Settings) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
load(): Promise<Settings> {
|
||||||
|
return Promise.resolve(this.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('AddonEnabledUseCase', () => {
|
||||||
|
let repository: MockSettingRepository;
|
||||||
|
let client: MockSettingClient;
|
||||||
|
let sut: SettingUseCase;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
let testSettings = {
|
||||||
|
keymaps: {},
|
||||||
|
search: {
|
||||||
|
default: 'google',
|
||||||
|
engines: {
|
||||||
|
google: 'https://google.com/?q={}',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
hintchars: 'abcd1234',
|
||||||
|
smoothscroll: false,
|
||||||
|
complete: 'sbh',
|
||||||
|
},
|
||||||
|
blacklist: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
repository = new MockSettingRepository();
|
||||||
|
client = new MockSettingClient(testSettings);
|
||||||
|
sut = new SettingUseCase({ repository, client });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#reload', () => {
|
||||||
|
it('loads settings and store to repository', async() => {
|
||||||
|
let settings = await sut.reload();
|
||||||
|
expect(settings.properties.hintchars).to.equal('abcd1234');
|
||||||
|
|
||||||
|
let saved = repository.get();
|
||||||
|
expect(saved.properties.hintchars).to.equal('abcd1234');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Reference in a new issue