aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/background/controllers/SettingController.ts2
-rw-r--r--src/background/infrastructures/ContentMessageListener.ts4
-rw-r--r--src/background/repositories/PersistentSettingRepository.ts2
-rw-r--r--src/background/repositories/SettingRepository.ts11
-rw-r--r--src/background/usecases/CompletionsUseCase.ts4
-rw-r--r--src/background/usecases/SettingUseCase.ts2
-rw-r--r--src/background/usecases/parsers.ts4
-rw-r--r--src/content/InputDriver.ts36
-rw-r--r--src/content/client/FollowMasterClient.ts4
-rw-r--r--src/content/client/SettingClient.ts4
-rw-r--r--src/content/controllers/FollowKeyController.ts2
-rw-r--r--src/content/controllers/KeymapController.ts2
-rw-r--r--src/content/controllers/MarkKeyController.ts2
-rw-r--r--src/content/controllers/SettingController.ts6
-rw-r--r--src/content/domains/Key.ts72
-rw-r--r--src/content/domains/KeySequence.ts64
-rw-r--r--src/content/repositories/KeymapRepository.ts8
-rw-r--r--src/content/repositories/SettingRepository.ts2
-rw-r--r--src/content/usecases/FollowSlaveUseCase.ts2
-rw-r--r--src/content/usecases/KeymapUseCase.ts24
-rw-r--r--src/content/usecases/SettingUseCase.ts2
-rw-r--r--src/settings/actions/index.ts8
-rw-r--r--src/settings/actions/setting.ts6
-rw-r--r--src/settings/components/form/BlacklistForm.tsx38
-rw-r--r--src/settings/components/form/KeymapsForm.tsx2
-rw-r--r--src/settings/components/form/PropertiesForm.tsx6
-rw-r--r--src/settings/components/form/SearchForm.tsx4
-rw-r--r--src/settings/components/index.tsx38
-rw-r--r--src/settings/reducers/setting.ts8
-rw-r--r--src/settings/storage.ts2
-rw-r--r--src/shared/SettingData.ts224
-rw-r--r--src/shared/Settings.ts200
-rw-r--r--src/shared/blacklists.ts13
-rw-r--r--src/shared/properties.ts50
-rw-r--r--src/shared/property-defs.ts56
-rw-r--r--src/shared/settings/Blacklist.ts39
-rw-r--r--src/shared/settings/Key.ts61
-rw-r--r--src/shared/settings/KeySequence.ts54
-rw-r--r--src/shared/settings/Keymaps.ts37
-rw-r--r--src/shared/settings/Properties.ts110
-rw-r--r--src/shared/settings/Search.ts76
-rw-r--r--src/shared/settings/Settings.ts158
-rw-r--r--src/shared/urls.ts4
-rw-r--r--src/shared/utils/re.ts6
-rw-r--r--test/content/InputDriver.test.ts69
-rw-r--r--test/content/domains/Key.test.ts137
-rw-r--r--test/content/domains/KeySequence.test.ts72
-rw-r--r--test/content/repositories/KeymapRepository.test.ts24
-rw-r--r--test/content/repositories/SettingRepository.test.ts8
-rw-r--r--test/content/usecases/SettingUseCaase.test.ts2
-rw-r--r--test/settings/components/form/KeymapsForm.test.tsx4
-rw-r--r--test/settings/components/form/PropertiesForm.test.tsx4
-rw-r--r--test/settings/components/form/SearchEngineForm.test.tsx8
-rw-r--r--test/shared/SettingData.test.ts51
-rw-r--r--test/shared/Settings.test.ts194
-rw-r--r--test/shared/blacklists.test.ts49
-rw-r--r--test/shared/properties.test.js18
-rw-r--r--test/shared/property-defs.test.js18
-rw-r--r--test/shared/settings/Blacklist.test.ts77
-rw-r--r--test/shared/settings/Key.test.ts92
-rw-r--r--test/shared/settings/KeySequence.test.ts72
-rw-r--r--test/shared/settings/Keymaps.test.ts66
-rw-r--r--test/shared/settings/Properties.test.ts30
-rw-r--r--test/shared/settings/Search.test.ts68
-rw-r--r--test/shared/settings/Settings.test.ts54
-rw-r--r--test/shared/urls.test.ts8
-rw-r--r--test/shared/utils/re.test.ts19
67 files changed, 1315 insertions, 1288 deletions
diff --git a/src/background/controllers/SettingController.ts b/src/background/controllers/SettingController.ts
index 34951ff..8d05852 100644
--- a/src/background/controllers/SettingController.ts
+++ b/src/background/controllers/SettingController.ts
@@ -1,7 +1,7 @@
import { injectable } from 'tsyringe';
import SettingUseCase from '../usecases/SettingUseCase';
import ContentMessageClient from '../infrastructures/ContentMessageClient';
-import Settings from '../../shared/Settings';
+import Settings from '../../shared/settings/Settings';
@injectable()
export default class SettingController {
diff --git a/src/background/infrastructures/ContentMessageListener.ts b/src/background/infrastructures/ContentMessageListener.ts
index 3d24741..f80d686 100644
--- a/src/background/infrastructures/ContentMessageListener.ts
+++ b/src/background/infrastructures/ContentMessageListener.ts
@@ -101,8 +101,8 @@ export default class ContentMessageListener {
return this.commandController.exec(text);
}
- onSettingsQuery(): Promise<any> {
- return this.settingController.getSetting();
+ async onSettingsQuery(): Promise<any> {
+ return (await this.settingController.getSetting()).toJSON();
}
onFindGetKeyword(): Promise<string> {
diff --git a/src/background/repositories/PersistentSettingRepository.ts b/src/background/repositories/PersistentSettingRepository.ts
index 927bce9..e3b78b3 100644
--- a/src/background/repositories/PersistentSettingRepository.ts
+++ b/src/background/repositories/PersistentSettingRepository.ts
@@ -8,7 +8,7 @@ export default class SettingRepository {
if (!settings) {
return null;
}
- return SettingData.valueOf(settings as any);
+ return SettingData.fromJSON(settings as any);
}
}
diff --git a/src/background/repositories/SettingRepository.ts b/src/background/repositories/SettingRepository.ts
index 2f159e5..e775a32 100644
--- a/src/background/repositories/SettingRepository.ts
+++ b/src/background/repositories/SettingRepository.ts
@@ -1,7 +1,7 @@
import { injectable } from 'tsyringe';
import MemoryStorage from '../infrastructures/MemoryStorage';
-import Settings from '../../shared/Settings';
-import * as PropertyDefs from '../../shared/property-defs';
+import Settings from '../../shared/settings/Settings';
+import Properties from '../../shared/settings/Properties';
const CACHED_SETTING_KEY = 'setting';
@@ -14,17 +14,18 @@ export default class SettingRepository {
}
get(): Promise<Settings> {
- return Promise.resolve(this.cache.get(CACHED_SETTING_KEY));
+ let data = this.cache.get(CACHED_SETTING_KEY);
+ return Promise.resolve(Settings.fromJSON(data));
}
update(value: Settings): void {
- return this.cache.set(CACHED_SETTING_KEY, value);
+ return this.cache.set(CACHED_SETTING_KEY, value.toJSON());
}
async setProperty(
name: string, value: string | number | boolean,
): Promise<void> {
- let def = PropertyDefs.defs.find(d => name === d.name);
+ let def = Properties.def(name);
if (!def) {
throw new Error('unknown property: ' + name);
}
diff --git a/src/background/usecases/CompletionsUseCase.ts b/src/background/usecases/CompletionsUseCase.ts
index 8cd4f32..bfff1e6 100644
--- a/src/background/usecases/CompletionsUseCase.ts
+++ b/src/background/usecases/CompletionsUseCase.ts
@@ -5,7 +5,7 @@ import CompletionsRepository from '../repositories/CompletionsRepository';
import * as filters from './filters';
import SettingRepository from '../repositories/SettingRepository';
import TabPresenter from '../presenters/TabPresenter';
-import * as PropertyDefs from '../../shared/property-defs';
+import Properties from '../../shared/settings/Properties';
const COMPLETION_ITEM_LIMIT = 10;
@@ -129,7 +129,7 @@ export default class CompletionsUseCase {
}
querySet(name: string, keywords: string): Promise<CompletionGroup[]> {
- let items = PropertyDefs.defs.map((def) => {
+ let items = Properties.defs().map((def) => {
if (def.type === 'boolean') {
return [
{
diff --git a/src/background/usecases/SettingUseCase.ts b/src/background/usecases/SettingUseCase.ts
index d73521f..d78d440 100644
--- a/src/background/usecases/SettingUseCase.ts
+++ b/src/background/usecases/SettingUseCase.ts
@@ -3,7 +3,7 @@ import PersistentSettingRepository
from '../repositories/PersistentSettingRepository';
import SettingRepository from '../repositories/SettingRepository';
import { DefaultSettingData } from '../../shared/SettingData';
-import Settings from '../../shared/Settings';
+import Settings from '../../shared/settings/Settings';
import NotifyPresenter from '../presenters/NotifyPresenter';
@injectable()
diff --git a/src/background/usecases/parsers.ts b/src/background/usecases/parsers.ts
index 6135fd8..e8a1149 100644
--- a/src/background/usecases/parsers.ts
+++ b/src/background/usecases/parsers.ts
@@ -1,4 +1,4 @@
-import * as PropertyDefs from '../../shared//property-defs';
+import Properties from '../../shared/settings/Properties';
const mustNumber = (v: any): number => {
let num = Number(v);
@@ -16,7 +16,7 @@ const parseSetOption = (
value = !key.startsWith('no');
key = value ? key : key.slice(2);
}
- let def = PropertyDefs.defs.find(d => d.name === key);
+ let def = Properties.def(key);
if (!def) {
throw new Error('Unknown property: ' + key);
}
diff --git a/src/content/InputDriver.ts b/src/content/InputDriver.ts
index 0472088..bc184d2 100644
--- a/src/content/InputDriver.ts
+++ b/src/content/InputDriver.ts
@@ -1,5 +1,5 @@
import * as dom from '../shared/utils/dom';
-import Key, * as keys from './domains/Key';
+import Key from '../shared/settings/Key';
const cancelKey = (e: KeyboardEvent): boolean => {
if (e.key === 'Escape') {
@@ -11,6 +11,38 @@ const cancelKey = (e: KeyboardEvent): boolean => {
return false;
};
+const modifiedKeyName = (name: string): string => {
+ if (name === ' ') {
+ return 'Space';
+ }
+ if (name.length === 1) {
+ return name;
+ } else if (name === 'Escape') {
+ return 'Esc';
+ }
+ return name;
+};
+
+// visible for testing
+export const keyFromKeyboardEvent = (e: KeyboardEvent): Key => {
+ let key = modifiedKeyName(e.key);
+ let shift = e.shiftKey;
+ if (key.length === 1 && key.toUpperCase() === key.toLowerCase()) {
+ // make shift false for symbols to enable key bindings by symbold keys.
+ // But this limits key bindings by symbol keys with Shift
+ // (such as Shift+$>.
+ shift = false;
+ }
+
+ return new Key({
+ key: modifiedKeyName(e.key),
+ shift: shift,
+ ctrl: e.ctrlKey,
+ alt: e.altKey,
+ meta: e.metaKey,
+ });
+};
+
export default class InputDriver {
private pressed: {[key: string]: string} = {};
@@ -66,7 +98,7 @@ export default class InputDriver {
return;
}
- let key = keys.fromKeyboardEvent(e);
+ let key = keyFromKeyboardEvent(e);
for (let listener of this.onKeyListeners) {
let stop = listener(key);
if (stop) {
diff --git a/src/content/client/FollowMasterClient.ts b/src/content/client/FollowMasterClient.ts
index da75308..6681e8a 100644
--- a/src/content/client/FollowMasterClient.ts
+++ b/src/content/client/FollowMasterClient.ts
@@ -1,5 +1,5 @@
import * as messages from '../../shared/messages';
-import Key from '../domains/Key';
+import Key from '../../shared/settings/Key';
export default interface FollowMasterClient {
startFollow(newTab: boolean, background: boolean): void;
@@ -35,7 +35,7 @@ export class FollowMasterClientImpl implements FollowMasterClient {
this.postMessage({
type: messages.FOLLOW_KEY_PRESS,
key: key.key,
- ctrlKey: key.ctrlKey || false,
+ ctrlKey: key.ctrl || false,
});
}
diff --git a/src/content/client/SettingClient.ts b/src/content/client/SettingClient.ts
index 0850f11..fc62720 100644
--- a/src/content/client/SettingClient.ts
+++ b/src/content/client/SettingClient.ts
@@ -1,4 +1,4 @@
-import Settings from '../../shared/Settings';
+import Settings from '../../shared/settings/Settings';
import * as messages from '../../shared/messages';
export default interface SettingClient {
@@ -10,6 +10,6 @@ export class SettingClientImpl {
let settings = await browser.runtime.sendMessage({
type: messages.SETTINGS_QUERY,
});
- return settings as Settings;
+ return Settings.fromJSON(settings);
}
}
diff --git a/src/content/controllers/FollowKeyController.ts b/src/content/controllers/FollowKeyController.ts
index 59d2271..0fd94ff 100644
--- a/src/content/controllers/FollowKeyController.ts
+++ b/src/content/controllers/FollowKeyController.ts
@@ -1,6 +1,6 @@
import { injectable } from 'tsyringe';
import FollowSlaveUseCase from '../usecases/FollowSlaveUseCase';
-import Key from '../domains/Key';
+import Key from '../../shared/settings/Key';
@injectable()
export default class FollowKeyController {
diff --git a/src/content/controllers/KeymapController.ts b/src/content/controllers/KeymapController.ts
index fcfaff1..6157a71 100644
--- a/src/content/controllers/KeymapController.ts
+++ b/src/content/controllers/KeymapController.ts
@@ -9,7 +9,7 @@ import ClipboardUseCase from '../usecases/ClipboardUseCase';
import OperationClient from '../client/OperationClient';
import MarkKeyyUseCase from '../usecases/MarkKeyUseCase';
import FollowMasterClient from '../client/FollowMasterClient';
-import Key from '../domains/Key';
+import Key from '../../shared/settings/Key';
@injectable()
export default class KeymapController {
diff --git a/src/content/controllers/MarkKeyController.ts b/src/content/controllers/MarkKeyController.ts
index 886e5ff..e7653ee 100644
--- a/src/content/controllers/MarkKeyController.ts
+++ b/src/content/controllers/MarkKeyController.ts
@@ -1,7 +1,7 @@
import { injectable } from 'tsyringe';
import MarkUseCase from '../usecases/MarkUseCase';
import MarkKeyyUseCase from '../usecases/MarkKeyUseCase';
-import Key from '../domains/Key';
+import Key from '../../shared/settings/Key';
@injectable()
export default class MarkKeyController {
diff --git a/src/content/controllers/SettingController.ts b/src/content/controllers/SettingController.ts
index 7fb045b..06273a0 100644
--- a/src/content/controllers/SettingController.ts
+++ b/src/content/controllers/SettingController.ts
@@ -1,8 +1,6 @@
import { injectable } from 'tsyringe';
import AddonEnabledUseCase from '../usecases/AddonEnabledUseCase';
import SettingUseCase from '../usecases/SettingUseCase';
-import * as blacklists from '../../shared/blacklists';
-
import * as messages from '../../shared/messages';
@injectable()
@@ -17,9 +15,7 @@ export default class SettingController {
async initSettings(): Promise<void> {
try {
let current = await this.settingUseCase.reload();
- let disabled = blacklists.includes(
- current.blacklist, window.location.href,
- );
+ let disabled = current.blacklist.includes(window.location.href);
if (disabled) {
this.addonEnabledUseCase.disable();
} else {
diff --git a/src/content/domains/Key.ts b/src/content/domains/Key.ts
deleted file mode 100644
index b25616e..0000000
--- a/src/content/domains/Key.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-export default interface Key {
- key: string;
- shiftKey?: boolean;
- ctrlKey?: boolean;
- altKey?: boolean;
- metaKey?: boolean;
-}
-
-const modifiedKeyName = (name: string): string => {
- if (name === ' ') {
- return 'Space';
- }
- if (name.length === 1) {
- return name;
- } else if (name === 'Escape') {
- return 'Esc';
- }
- return name;
-};
-
-export const fromKeyboardEvent = (e: KeyboardEvent): Key => {
- let key = modifiedKeyName(e.key);
- let shift = e.shiftKey;
- if (key.length === 1 && key.toUpperCase() === key.toLowerCase()) {
- // make shift false for symbols to enable key bindings by symbold keys.
- // But this limits key bindings by symbol keys with Shift (such as Shift+$>.
- shift = false;
- }
-
- return {
- key: modifiedKeyName(e.key),
- shiftKey: shift,
- ctrlKey: e.ctrlKey,
- altKey: e.altKey,
- metaKey: e.metaKey,
- };
-};
-
-export const fromMapKey = (key: string): Key => {
- if (key.startsWith('<') && key.endsWith('>')) {
- let inner = key.slice(1, -1);
- let shift = inner.includes('S-');
- let base = inner.slice(inner.lastIndexOf('-') + 1);
- if (shift && base.length === 1) {
- base = base.toUpperCase();
- } else if (!shift && base.length === 1) {
- base = base.toLowerCase();
- }
- return {
- key: base,
- shiftKey: inner.includes('S-'),
- ctrlKey: inner.includes('C-'),
- altKey: inner.includes('A-'),
- metaKey: inner.includes('M-'),
- };
- }
- return {
- key: key,
- shiftKey: key.toLowerCase() !== key,
- ctrlKey: false,
- altKey: false,
- metaKey: false,
- };
-};
-
-export const equals = (e1: Key, e2: Key): boolean => {
- return e1.key === e2.key &&
- e1.ctrlKey === e2.ctrlKey &&
- e1.metaKey === e2.metaKey &&
- e1.altKey === e2.altKey &&
- e1.shiftKey === e2.shiftKey;
-};
diff --git a/src/content/domains/KeySequence.ts b/src/content/domains/KeySequence.ts
deleted file mode 100644
index 6a05c2f..0000000
--- a/src/content/domains/KeySequence.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import Key, * as keyUtils from './Key';
-
-export default class KeySequence {
- private keys: Key[];
-
- private constructor(keys: Key[]) {
- this.keys = keys;
- }
-
- static from(keys: Key[]): KeySequence {
- return new KeySequence(keys);
- }
-
- push(key: Key): number {
- return this.keys.push(key);
- }
-
- length(): number {
- return this.keys.length;
- }
-
- startsWith(o: KeySequence): boolean {
- if (this.keys.length < o.keys.length) {
- return false;
- }
- for (let i = 0; i < o.keys.length; ++i) {
- if (!keyUtils.equals(this.keys[i], o.keys[i])) {
- return false;
- }
- }
- return true;
- }
-
- getKeyArray(): Key[] {
- return this.keys;
- }
-}
-
-export const fromMapKeys = (keys: string): KeySequence => {
- const fromMapKeysRecursive = (
- remainings: string, mappedKeys: Key[],
- ): Key[] => {
- if (remainings.length === 0) {
- return mappedKeys;
- }
-
- let nextPos = 1;
- if (remainings.startsWith('<')) {
- let ltPos = remainings.indexOf('>');
- if (ltPos > 0) {
- nextPos = ltPos + 1;
- }
- }
-
- return fromMapKeysRecursive(
- remainings.slice(nextPos),
- mappedKeys.concat([keyUtils.fromMapKey(remainings.slice(0, nextPos))])
- );
- };
-
- let data = fromMapKeysRecursive(keys, []);
- return KeySequence.from(data);
-};
-
diff --git a/src/content/repositories/KeymapRepository.ts b/src/content/repositories/KeymapRepository.ts
index d7b5b7d..3391229 100644
--- a/src/content/repositories/KeymapRepository.ts
+++ b/src/content/repositories/KeymapRepository.ts
@@ -1,5 +1,5 @@
-import Key from '../domains/Key';
-import KeySequence from '../domains/KeySequence';
+import Key from '../../shared/settings/Key';
+import KeySequence from '../../shared/settings/KeySequence';
export default interface KeymapRepository {
enqueueKey(key: Key): KeySequence;
@@ -7,7 +7,7 @@ export default interface KeymapRepository {
clear(): void;
}
-let current: KeySequence = KeySequence.from([]);
+let current: KeySequence = new KeySequence([]);
export class KeymapRepositoryImpl {
@@ -17,6 +17,6 @@ export class KeymapRepositoryImpl {
}
clear(): void {
- current = KeySequence.from([]);
+ current = new KeySequence([]);
}
}
diff --git a/src/content/repositories/SettingRepository.ts b/src/content/repositories/SettingRepository.ts
index d718794..4ba26e0 100644
--- a/src/content/repositories/SettingRepository.ts
+++ b/src/content/repositories/SettingRepository.ts
@@ -1,4 +1,4 @@
-import Settings, { DefaultSetting } from '../../shared/Settings';
+import Settings, { DefaultSetting } from '../../shared/settings/Settings';
let current: Settings = DefaultSetting;
diff --git a/src/content/usecases/FollowSlaveUseCase.ts b/src/content/usecases/FollowSlaveUseCase.ts
index 2bd16ee..d471adb 100644
--- a/src/content/usecases/FollowSlaveUseCase.ts
+++ b/src/content/usecases/FollowSlaveUseCase.ts
@@ -4,7 +4,7 @@ import FollowPresenter from '../presenters/FollowPresenter';
import TabsClient from '../client/TabsClient';
import FollowMasterClient from '../client/FollowMasterClient';
import { LinkHint, InputHint } from '../presenters/Hint';
-import Key from '../domains/Key';
+import Key from '../../shared/settings/Key';
interface Size {
width: number;
diff --git a/src/content/usecases/KeymapUseCase.ts b/src/content/usecases/KeymapUseCase.ts
index d0d039a..495f6d0 100644
--- a/src/content/usecases/KeymapUseCase.ts
+++ b/src/content/usecases/KeymapUseCase.ts
@@ -3,16 +3,16 @@ import KeymapRepository from '../repositories/KeymapRepository';
import SettingRepository from '../repositories/SettingRepository';
import AddonEnabledRepository from '../repositories/AddonEnabledRepository';
import * as operations from '../../shared/operations';
-import { Keymaps } from '../../shared/Settings';
-import Key from '../domains/Key';
-import KeySequence, * as keySequenceUtils from '../domains/KeySequence';
+import Keymaps from '../../shared/settings/Keymaps';
+import Key from '../../shared/settings/Key';
+import KeySequence from '../../shared/settings/KeySequence';
type KeymapEntityMap = Map<KeySequence, operations.Operation>;
-const reservedKeymaps: Keymaps = {
+const reservedKeymaps = Keymaps.fromJSON({
'<Esc>': { type: operations.CANCEL },
'<C-[>': { type: operations.CANCEL },
-};
+});
@injectable()
export default class KeymapUseCase {
@@ -65,16 +65,10 @@ export default class KeymapUseCase {
}
private keymapEntityMap(): KeymapEntityMap {
- let keymaps = {
- ...this.settingRepository.get().keymaps,
- ...reservedKeymaps,
- };
- let entries = Object.entries(keymaps).map((entry) => {
- return [
- keySequenceUtils.fromMapKeys(entry[0]),
- entry[1],
- ];
- }) as [KeySequence, operations.Operation][];
+ let keymaps = this.settingRepository.get().keymaps.combine(reservedKeymaps);
+ let entries = keymaps.entries().map(
+ ([keys, op]) => [KeySequence.fromMapKeys(keys), op]
+ ) as [KeySequence, operations.Operation][];
return new Map<KeySequence, operations.Operation>(entries);
}
}
diff --git a/src/content/usecases/SettingUseCase.ts b/src/content/usecases/SettingUseCase.ts
index d5f66c6..4608039 100644
--- a/src/content/usecases/SettingUseCase.ts
+++ b/src/content/usecases/SettingUseCase.ts
@@ -1,7 +1,7 @@
import { injectable, inject } from 'tsyringe';
import SettingRepository from '../repositories/SettingRepository';
import SettingClient from '../client/SettingClient';
-import Settings from '../../shared/Settings';
+import Settings from '../../shared/settings/Settings';
@injectable()
export default class SettingUseCase {
diff --git a/src/settings/actions/index.ts b/src/settings/actions/index.ts
index b1e996e..dfa41c4 100644
--- a/src/settings/actions/index.ts
+++ b/src/settings/actions/index.ts
@@ -1,5 +1,5 @@
import {
- JSONSettings, FormSettings, SettingSource,
+ JSONTextSettings, FormSettings, SettingSource,
} from '../../shared/SettingData';
// Settings
@@ -11,14 +11,14 @@ export const SETTING_SWITCH_TO_JSON = 'setting.switch.to.json';
interface SettingSetSettingsAcion {
type: typeof SETTING_SET_SETTINGS;
source: SettingSource;
- json?: JSONSettings;
+ json?: JSONTextSettings;
form?: FormSettings;
}
interface SettingShowErrorAction {
type: typeof SETTING_SHOW_ERROR;
error: string;
- json: JSONSettings;
+ json: JSONTextSettings;
}
interface SettingSwitchToFormAction {
@@ -28,7 +28,7 @@ interface SettingSwitchToFormAction {
interface SettingSwitchToJsonAction {
type: typeof SETTING_SWITCH_TO_JSON;
- json: JSONSettings,
+ json: JSONTextSettings,
}
export type SettingAction =
diff --git a/src/settings/actions/setting.ts b/src/settings/actions/setting.ts
index 9eb416e..9404791 100644
--- a/src/settings/actions/setting.ts
+++ b/src/settings/actions/setting.ts
@@ -1,7 +1,7 @@
import * as actions from './index';
import * as storages from '../storage';
import SettingData, {
- JSONSettings, FormSettings, SettingSource,
+ JSONTextSettings, FormSettings, SettingSource,
} from '../../shared/SettingData';
const load = async(): Promise<actions.SettingAction> => {
@@ -26,7 +26,7 @@ const save = async(data: SettingData): Promise<actions.SettingAction> => {
return set(data);
};
-const switchToForm = (json: JSONSettings): actions.SettingAction => {
+const switchToForm = (json: JSONTextSettings): actions.SettingAction => {
try {
// toSettings exercise validation
let form = FormSettings.fromSettings(json.toSettings());
@@ -44,7 +44,7 @@ const switchToForm = (json: JSONSettings): actions.SettingAction => {
};
const switchToJson = (form: FormSettings): actions.SettingAction => {
- let json = JSONSettings.fromSettings(form.toSettings());
+ let json = JSONTextSettings.fromSettings(form.toSettings());
return {
type: actions.SETTING_SWITCH_TO_JSON,
json,
diff --git a/src/settings/components/form/BlacklistForm.tsx b/src/settings/components/form/BlacklistForm.tsx
index 637bc1e..f352e41 100644
--- a/src/settings/components/form/BlacklistForm.tsx
+++ b/src/settings/components/form/BlacklistForm.tsx
@@ -2,10 +2,11 @@ import './BlacklistForm.scss';
import AddButton from '../ui/AddButton';
import DeleteButton from '../ui/DeleteButton';
import React from 'react';
+import { BlacklistJSON } from '../../../shared/settings/Blacklist';
interface Props {
- value: string[];
- onChange: (value: string[]) => void;
+ value: BlacklistJSON;
+ onChange: (value: BlacklistJSON) => void;
onBlur: () => void;
}
@@ -19,19 +20,24 @@ class BlacklistForm extends React.Component<Props> {
render() {
return <div className='form-blacklist-form'>
{
- this.props.value.map((url, index) => {
- return <div key={index} className='form-blacklist-form-row'>
- <input data-index={index} type='text' name='url'
- className='column-url' value={url}
- onChange={this.bindValue.bind(this)}
- onBlur={this.props.onBlur}
- />
- <DeleteButton data-index={index} name='delete'
- onClick={this.bindValue.bind(this)}
- onBlur={this.props.onBlur}
- />
- </div>;
- })
+ this.props.value
+ .map((item, index) => {
+ if (typeof item !== 'string') {
+ // TODO support partial blacklist;
+ return null;
+ }
+ return <div key={index} className='form-blacklist-form-row'>
+ <input data-index={index} type='text' name='url'
+ className='column-url' value={item}
+ onChange={this.bindValue.bind(this)}
+ onBlur={this.props.onBlur}
+ />
+ <DeleteButton data-index={index} name='delete'
+ onClick={this.bindValue.bind(this)}
+ onBlur={this.props.onBlur}
+ />
+ </div>;
+ })
}
<AddButton name='add' style={{ float: 'right' }}
onClick={this.bindValue.bind(this)} />
@@ -41,7 +47,7 @@ class BlacklistForm extends React.Component<Props> {
bindValue(e: any) {
let name = e.target.name;
let index = e.target.getAttribute('data-index');
- let next = this.props.value ? this.props.value.slice() : [];
+ let next = this.props.value.slice();
if (name === 'url') {
next[index] = e.target.value;
diff --git a/src/settings/components/form/KeymapsForm.tsx b/src/settings/components/form/KeymapsForm.tsx
index 3cba0c0..94934ae 100644
--- a/src/settings/components/form/KeymapsForm.tsx
+++ b/src/settings/components/form/KeymapsForm.tsx
@@ -12,7 +12,7 @@ interface Props {
class KeymapsForm extends React.Component<Props> {
public static defaultProps: Props = {
- value: FormKeymaps.valueOf({}),
+ value: FormKeymaps.fromJSON({}),
onChange: () => {},
onBlur: () => {},
};
diff --git a/src/settings/components/form/PropertiesForm.tsx b/src/settings/components/form/PropertiesForm.tsx
index ee98b7e..db8c8e5 100644
--- a/src/settings/components/form/PropertiesForm.tsx
+++ b/src/settings/components/form/PropertiesForm.tsx
@@ -18,7 +18,7 @@ class PropertiesForm extends React.Component<Props> {
render() {
let types = this.props.types;
- let value = this.props.value;
+ let values = this.props.value;
return <div className='form-properties-form'>
{
@@ -46,10 +46,10 @@ class PropertiesForm extends React.Component<Props> {
<span className='column-name'>{name}</span>
<input type={inputType} name={name}
className='column-input'
- value={value[name] ? value[name] : ''}
+ value={values[name] ? values[name] : ''}
onChange={onChange}
onBlur={this.props.onBlur}
- checked={value[name]}
+ checked={values[name]}
/>
</label>
</div>;
diff --git a/src/settings/components/form/SearchForm.tsx b/src/settings/components/form/SearchForm.tsx
index 6ba6cfb..0aaf6fd 100644
--- a/src/settings/components/form/SearchForm.tsx
+++ b/src/settings/components/form/SearchForm.tsx
@@ -12,7 +12,7 @@ interface Props {
class SearchForm extends React.Component<Props> {
public static defaultProps: Props = {
- value: FormSearch.valueOf({ default: '', engines: []}),
+ value: FormSearch.fromJSON({ default: '', engines: []}),
onChange: () => {},
onBlur: () => {},
};
@@ -81,7 +81,7 @@ class SearchForm extends React.Component<Props> {
}
}
- this.props.onChange(FormSearch.valueOf(next));
+ this.props.onChange(FormSearch.fromJSON(next));
if (name === 'delete' || name === 'default') {
this.props.onBlur();
}
diff --git a/src/settings/components/index.tsx b/src/settings/components/index.tsx
index eeac2cf..160dd9c 100644
--- a/src/settings/components/index.tsx
+++ b/src/settings/components/index.tsx
@@ -8,11 +8,11 @@ import BlacklistForm from './form/BlacklistForm';
import PropertiesForm from './form/PropertiesForm';
import * as settingActions from '../../settings/actions/setting';
import SettingData, {
- JSONSettings, FormKeymaps, FormSearch, FormSettings,
+ FormKeymaps, FormSearch, FormSettings, JSONTextSettings,
} from '../../shared/SettingData';
import { State as AppState } from '../reducers/setting';
-import * as settings from '../../shared/Settings';
-import * as PropertyDefs from '../../shared/property-defs';
+import Properties from '../../shared/settings/Properties';
+import Blacklist from '../../shared/settings/Blacklist';
const DO_YOU_WANT_TO_CONTINUE =
'Some settings in JSON can be lost when migrating. ' +
@@ -32,12 +32,7 @@ class SettingsComponent extends React.Component<Props> {
this.props.dispatch(settingActions.load());
}
- renderFormFields(form: any) {
- let types = PropertyDefs.defs.reduce(
- (o: {[key: string]: string}, def) => {
- o[def.name] = def.type;
- return o;
- }, {});
+ renderFormFields(form: FormSettings) {
return <div>
<fieldset>
<legend>Keybindings</legend>
@@ -58,7 +53,7 @@ class SettingsComponent extends React.Component<Props> {
<fieldset>
<legend>Blacklist</legend>
<BlacklistForm
- value={form.blacklist}
+ value={form.blacklist.toJSON()}
onChange={this.bindBlacklistForm.bind(this)}
onBlur={this.save.bind(this)}
/>
@@ -66,8 +61,8 @@ class SettingsComponent extends React.Component<Props> {
<fieldset>
<legend>Properties</legend>
<PropertiesForm
- types={types}
- value={form.properties}
+ types={Properties.types()}
+ value={form.properties.toJSON()}
onChange={this.bindPropertiesForm.bind(this)}
onBlur={this.save.bind(this)}
/>
@@ -75,7 +70,7 @@ class SettingsComponent extends React.Component<Props> {
</div>;
}
- renderJsonFields(json: JSONSettings, error: string) {
+ renderJsonFields(json: JSONTextSettings, error: string) {
return <div>
<Input
type='textarea'
@@ -85,7 +80,7 @@ class SettingsComponent extends React.Component<Props> {
error={error}
onValueChange={this.bindJson.bind(this)}
onBlur={this.save.bind(this)}
- value={json.toJSON()}
+ value={json.toJSONText()}
/>
</div>;
}
@@ -94,10 +89,9 @@ class SettingsComponent extends React.Component<Props> {
let fields = null;
let disabled = this.props.error.length > 0;
if (this.props.source === 'form') {
- fields = this.renderFormFields(this.props.form);
+ fields = this.renderFormFields(this.props.form!!);
} else if (this.props.source === 'json') {
- fields = this.renderJsonFields(
- this.props.json as JSONSettings, this.props.error);
+ fields = this.renderJsonFields(this.props.json!!, this.props.error);
}
return (
<div>
@@ -139,7 +133,7 @@ class SettingsComponent extends React.Component<Props> {
let data = new SettingData({
source: this.props.source,
form: (this.props.form as FormSettings).buildWithSearch(
- FormSearch.valueOf(value)),
+ FormSearch.fromJSON(value)),
});
this.props.dispatch(settingActions.set(data));
}
@@ -148,7 +142,7 @@ class SettingsComponent extends React.Component<Props> {
let data = new SettingData({
source: this.props.source,
form: (this.props.form as FormSettings).buildWithBlacklist(
- settings.blacklistValueOf(value)),
+ Blacklist.fromJSON(value)),
});
this.props.dispatch(settingActions.set(data));
}
@@ -157,7 +151,7 @@ class SettingsComponent extends React.Component<Props> {
let data = new SettingData({
source: this.props.source,
form: (this.props.form as FormSettings).buildWithProperties(
- settings.propertiesValueOf(value)),
+ Properties.fromJSON(value))
});
this.props.dispatch(settingActions.set(data));
}
@@ -165,7 +159,7 @@ class SettingsComponent extends React.Component<Props> {
bindJson(_name: string, value: string) {
let data = new SettingData({
source: this.props.source,
- json: JSONSettings.valueOf(value),
+ json: JSONTextSettings.fromText(value),
});
this.props.dispatch(settingActions.set(data));
}
@@ -183,7 +177,7 @@ class SettingsComponent extends React.Component<Props> {
return;
}
this.props.dispatch(
- settingActions.switchToForm(this.props.json as JSONSettings));
+ settingActions.switchToForm(this.props.json as JSONTextSettings));
this.save();
}
}
diff --git a/src/settings/reducers/setting.ts b/src/settings/reducers/setting.ts
index c4a21c7..804853f 100644
--- a/src/settings/reducers/setting.ts
+++ b/src/settings/reducers/setting.ts
@@ -1,18 +1,20 @@
import * as actions from '../actions';
import {
- JSONSettings, FormSettings, SettingSource,
+ JSONTextSettings, FormSettings, SettingSource,
} from '../../shared/SettingData';
+import { DefaultSetting } from '../../shared/settings/Settings';
export interface State {
source: SettingSource;
- json?: JSONSettings;
+ json?: JSONTextSettings;
form?: FormSettings;
error: string;
}
const defaultState: State = {
source: SettingSource.JSON,
- json: JSONSettings.valueOf(''),
+ json: JSONTextSettings.fromText(''),
+ form: FormSettings.fromSettings(DefaultSetting),
error: '',
};
diff --git a/src/settings/storage.ts b/src/settings/storage.ts
index 32b6351..2a983df 100644
--- a/src/settings/storage.ts
+++ b/src/settings/storage.ts
@@ -6,7 +6,7 @@ export const load = async(): Promise<SettingData> => {
return DefaultSettingData;
}
try {
- return SettingData.valueOf(settings as any);
+ return SettingData.fromJSON(settings as any);
} catch (e) {
console.error('unable to load settings', e);
return DefaultSettingData;
diff --git a/src/shared/SettingData.ts b/src/shared/SettingData.ts
index 14a7d35..532570e 100644
--- a/src/shared/SettingData.ts
+++ b/src/shared/SettingData.ts
@@ -1,15 +1,19 @@
import * as operations from './operations';
-import Settings, * as settings from './Settings';
+import Settings, { DefaultSettingJSONText } from './settings/Settings';
+import Keymaps from './settings/Keymaps';
+import Search from './settings/Search';
+import Properties from './settings/Properties';
+import Blacklist from './settings/Blacklist';
export class FormKeymaps {
- private data: {[op: string]: string};
+ private readonly data: {[op: string]: string};
- constructor(data: {[op: string]: string}) {
+ private constructor(data: {[op: string]: string}) {
this.data = data;
}
- toKeymaps(): settings.Keymaps {
- let keymaps: settings.Keymaps = {};
+ toKeymaps(): Keymaps {
+ let keymaps: { [key: string]: operations.Operation } = {};
for (let name of Object.keys(this.data)) {
let [type, argStr] = name.split('?');
let args = {};
@@ -19,7 +23,7 @@ export class FormKeymaps {
let key = this.data[name];
keymaps[key] = operations.valueOf({ type, ...args });
}
- return keymaps;
+ return Keymaps.fromJSON(keymaps);
}
toJSON(): {[op: string]: string} {
@@ -34,7 +38,7 @@ export class FormKeymaps {
return new FormKeymaps(newData);
}
- static valueOf(o: ReturnType<FormKeymaps['toJSON']>): FormKeymaps {
+ static fromJSON(o: ReturnType<FormKeymaps['toJSON']>): FormKeymaps {
let data: {[op: string]: string} = {};
for (let op of Object.keys(o)) {
data[op] = o[op] as string;
@@ -42,10 +46,11 @@ export class FormKeymaps {
return new FormKeymaps(data);
}
- static fromKeymaps(keymaps: settings.Keymaps): FormKeymaps {
+ static fromKeymaps(keymaps: Keymaps): FormKeymaps {
+ let json = keymaps.toJSON();
let data: {[op: string]: string} = {};
- for (let key of Object.keys(keymaps)) {
- let op = keymaps[key];
+ for (let key of Object.keys(json)) {
+ let op = json[key];
let args = { ...op };
delete args.type;
@@ -60,24 +65,21 @@ export class FormKeymaps {
}
export class FormSearch {
- private default: string;
+ private readonly default: string;
- private engines: string[][];
+ private readonly engines: string[][];
constructor(defaultEngine: string, engines: string[][]) {
this.default = defaultEngine;
this.engines = engines;
}
- toSearchSettings(): settings.Search {
- return {
- default: this.default,
- engines: this.engines.reduce(
- (o: {[key: string]: string}, [name, url]) => {
- o[name] = url;
- return o;
- }, {}),
- };
+ toSearchSettings(): Search {
+ let engines: { [name: string]: string } = {};
+ for (let entry of this.engines) {
+ engines[entry[0]] = entry[1];
+ }
+ return new Search(this.default, engines);
}
toJSON(): {
@@ -90,7 +92,7 @@ export class FormSearch {
};
}
- static valueOf(o: ReturnType<FormSearch['toJSON']>): FormSearch {
+ static fromJSON(o: ReturnType<FormSearch['toJSON']>): FormSearch {
if (!Object.prototype.hasOwnProperty.call(o, 'default')) {
throw new TypeError(`"default" field not set`);
}
@@ -100,53 +102,58 @@ export class FormSearch {
return new FormSearch(o.default, o.engines);
}
- static fromSearch(search: settings.Search): FormSearch {
+ static fromSearch(search: Search): FormSearch {
let engines = Object.entries(search.engines).reduce(
(o: string[][], [name, url]) => {
return o.concat([[name, url]]);
}, []);
- return new FormSearch(search.default, engines);
+ return new FormSearch(search.defaultEngine, engines);
}
}
-export class JSONSettings {
- private json: string;
-
- constructor(json: any) {
- this.json = json;
+export class JSONTextSettings {
+ constructor(
+ private json: string,
+ ) {
}
toSettings(): Settings {
- return settings.valueOf(JSON.parse(this.json));
+ return Settings.fromJSON(JSON.parse(this.json));
}
- toJSON(): string {
+ toJSONText(): string {
return this.json;
}
- static valueOf(o: ReturnType<JSONSettings['toJSON']>): JSONSettings {
- return new JSONSettings(o);
+ static fromText(o: string): JSONTextSettings {
+ return new JSONTextSettings(o);
}
- static fromSettings(data: Settings): JSONSettings {
- return new JSONSettings(JSON.stringify(data, undefined, 2));
+ static fromSettings(data: Settings): JSONTextSettings {
+ let json = {
+ keymaps: data.keymaps.toJSON(),
+ search: data.search,
+ properties: data.properties,
+ blacklist: data.blacklist,
+ };
+ return new JSONTextSettings(JSON.stringify(json, undefined, 2));
}
}
export class FormSettings {
- private keymaps: FormKeymaps;
+ public readonly keymaps: FormKeymaps;
- private search: FormSearch;
+ public readonly search: FormSearch;
- private properties: settings.Properties;
+ public readonly properties: Properties;
- private blacklist: string[];
+ public readonly blacklist: Blacklist;
constructor(
keymaps: FormKeymaps,
search: FormSearch,
- properties: settings.Properties,
- blacklist: string[],
+ properties: Properties,
+ blacklist: Blacklist,
) {
this.keymaps = keymaps;
this.search = search;
@@ -172,7 +179,7 @@ export class FormSettings {
);
}
- buildWithProperties(props: settings.Properties): FormSettings {
+ buildWithProperties(props: Properties): FormSettings {
return new FormSettings(
this.keymaps,
this.search,
@@ -181,7 +188,7 @@ export class FormSettings {
);
}
- buildWithBlacklist(blacklist: string[]): FormSettings {
+ buildWithBlacklist(blacklist: Blacklist): FormSettings {
return new FormSettings(
this.keymaps,
this.search,
@@ -191,39 +198,39 @@ export class FormSettings {
}
toSettings(): Settings {
- return settings.valueOf({
- keymaps: this.keymaps.toKeymaps(),
- search: this.search.toSearchSettings(),
- properties: this.properties,
- blacklist: this.blacklist,
+ return Settings.fromJSON({
+ keymaps: this.keymaps.toKeymaps().toJSON(),
+ search: this.search.toSearchSettings().toJSON(),
+ properties: this.properties.toJSON(),
+ blacklist: this.blacklist.toJSON(),
});
}
toJSON(): {
keymaps: ReturnType<FormKeymaps['toJSON']>;
search: ReturnType<FormSearch['toJSON']>;
- properties: settings.Properties;
- blacklist: string[];
+ properties: ReturnType<Properties['toJSON']>;
+ blacklist: ReturnType<Blacklist['toJSON']>;
} {
return {
keymaps: this.keymaps.toJSON(),
search: this.search.toJSON(),
- properties: this.properties,
- blacklist: this.blacklist,
+ properties: this.properties.toJSON(),
+ blacklist: this.blacklist.toJSON(),
};
}
- static valueOf(o: ReturnType<FormSettings['toJSON']>): FormSettings {
+ static fromJSON(o: ReturnType<FormSettings['toJSON']>): FormSettings {
for (let name of ['keymaps', 'search', 'properties', 'blacklist']) {
if (!Object.prototype.hasOwnProperty.call(o, name)) {
throw new Error(`"${name}" field not set`);
}
}
return new FormSettings(
- FormKeymaps.valueOf(o.keymaps),
- FormSearch.valueOf(o.search),
- settings.propertiesValueOf(o.properties),
- settings.blacklistValueOf(o.blacklist),
+ FormKeymaps.fromJSON(o.keymaps),
+ FormSearch.fromJSON(o.search),
+ Properties.fromJSON(o.properties),
+ Blacklist.fromJSON(o.blacklist),
);
}
@@ -244,7 +251,7 @@ export enum SettingSource {
export default class SettingData {
private source: SettingSource;
- private json?: JSONSettings;
+ private json?: JSONTextSettings;
private form?: FormSettings;
@@ -252,7 +259,7 @@ export default class SettingData {
source, json, form
}: {
source: SettingSource,
- json?: JSONSettings,
+ json?: JSONTextSettings,
form?: FormSettings,
}) {
this.source = source;
@@ -264,7 +271,7 @@ export default class SettingData {
return this.source;
}
- getJSON(): JSONSettings {
+ getJSON(): JSONTextSettings {
if (!this.json) {
throw new TypeError('json settings not set');
}
@@ -283,7 +290,7 @@ export default class SettingData {
case SettingSource.JSON:
return {
source: this.source,
- json: (this.json as JSONSettings).toJSON(),
+ json: (this.json as JSONTextSettings).toJSONText(),
};
case SettingSource.Form:
return {
@@ -304,7 +311,7 @@ export default class SettingData {
throw new Error(`unknown settings source: ${this.source}`);
}
- static valueOf(o: {
+ static fromJSON(o: {
source: string;
json?: string;
form?: ReturnType<FormSettings['toJSON']>;
@@ -313,13 +320,13 @@ export default class SettingData {
case SettingSource.JSON:
return new SettingData({
source: o.source,
- json: JSONSettings.valueOf(
- o.json as ReturnType<JSONSettings['toJSON']>),
+ json: JSONTextSettings.fromText(
+ o.json as ReturnType<JSONTextSettings['toJSONText']>),
});
case SettingSource.Form:
return new SettingData({
source: o.source,
- form: FormSettings.valueOf(
+ form: FormSettings.fromJSON(
o.form as ReturnType<FormSettings['toJSON']>),
});
}
@@ -327,90 +334,7 @@ export default class SettingData {
}
}
-export const DefaultSettingData: SettingData = SettingData.valueOf({
+export const DefaultSettingData: SettingData = SettingData.fromJSON({
source: 'json',
- json: `{
- "keymaps": {
- "0": { "type": "scroll.home" },
- ":": { "type": "command.show" },
- "o": { "type": "command.show.open", "alter": false },
- "O": { "type": "command.show.open", "alter": true },
- "t": { "type": "command.show.tabopen", "alter": false },
- "T": { "type": "command.show.tabopen", "alter": true },
- "w": { "type": "command.show.winopen", "alter": false },
- "W": { "type": "command.show.winopen", "alter": true },
- "b": { "type": "command.show.buffer" },
- "a": { "type": "command.show.addbookmark", "alter": true },
- "k": { "type": "scroll.vertically", "count": -1 },
- "j": { "type": "scroll.vertically", "count": 1 },
- "h": { "type": "scroll.horizonally", "count": -1 },
- "l": { "type": "scroll.horizonally", "count": 1 },
- "<C-U>": { "type": "scroll.pages", "count": -0.5 },
- "<C-D>": { "type": "scroll.pages", "count": 0.5 },
- "<C-B>": { "type": "scroll.pages", "count": -1 },
- "<C-F>": { "type": "scroll.pages", "count": 1 },
- "gg": { "type": "scroll.top" },
- "G": { "type": "scroll.bottom" },
- "$": { "type": "scroll.end" },
- "d": { "type": "tabs.close" },
- "D": { "type": "tabs.close", "select": "left" },
- "x$": { "type": "tabs.close.right" },
- "!d": { "type": "tabs.close.force" },
- "u": { "type": "tabs.reopen" },
- "K": { "type": "tabs.prev" },
- "J": { "type": "tabs.next" },
- "gT": { "type": "tabs.prev" },
- "gt": { "type": "tabs.next" },
- "g0": { "type": "tabs.first" },
- "g$": { "type": "tabs.last" },
- "<C-6>": { "type": "tabs.prevsel" },
- "r": { "type": "tabs.reload", "cache": false },
- "R": { "type": "tabs.reload", "cache": true },
- "zp": { "type": "tabs.pin.toggle" },
- "zd": { "type": "tabs.duplicate" },
- "zi": { "type": "zoom.in" },
- "zo": { "type": "zoom.out" },
- "zz": { "type": "zoom.neutral" },
- "f": { "type": "follow.start", "newTab": false },
- "F": { "type": "follow.start", "newTab": true, "background": false },
- "m": { "type": "mark.set.prefix" },
- "'": { "type": "mark.jump.prefix" },
- "H": { "type": "navigate.history.prev" },
- "L": { "type": "navigate.history.next" },
- "[[": { "type": "navigate.link.prev" },
- "]]": { "type": "navigate.link.next" },
- "gu": { "type": "navigate.parent" },
- "gU": { "type": "navigate.root" },
- "gi": { "type": "focus.input" },
- "gf": { "type": "page.source" },
- "gh": { "type": "page.home" },
- "gH": { "type": "page.home", "newTab": true },
- "y": { "type": "urls.yank" },
- "p": { "type": "urls.paste", "newTab": false },
- "P": { "type": "urls.paste", "newTab": true },
- "/": { "type": "find.start" },
- "n": { "type": "find.next" },
- "N": { "type": "find.prev" },
- ".": { "type": "repeat.last" },
- "<S-Esc>": { "type": "addon.toggle.enabled" }
- },
- "search": {
- "default": "google",
- "engines": {
- "google": "https://google.com/search?q={}",
- "yahoo": "https://search.yahoo.com/search?p={}",
- "bing": "https://www.bing.com/search?q={}",
- "duckduckgo": "https://duckduckgo.com/?q={}",
- "twitter": "https://twitter.com/search?q={}",
- "wikipedia": "https://en.wikipedia.org/w/index.php?search={}"
- }
- },
- "properties": {
- "hintchars": "abcdefghijklmnopqrstuvwxyz",
- "smoothscroll": false,
- "complete": "sbh"
- },
- "blacklist": [
- ]
-}`,
+ json: DefaultSettingJSONText,
});
diff --git a/src/shared/Settings.ts b/src/shared/Settings.ts
deleted file mode 100644
index d338e2a..0000000
--- a/src/shared/Settings.ts
+++ /dev/null
@@ -1,200 +0,0 @@
-import * as operations from './operations';
-import * as PropertyDefs from './property-defs';
-
-export type Keymaps = {[key: string]: operations.Operation};
-
-export interface Search {
- default: string;
- engines: { [key: string]: string };
-}
-
-export interface Properties {
- hintchars: string;
- smoothscroll: boolean;
- complete: string;
-}
-
-export default interface Settings {
- keymaps: Keymaps;
- search: Search;
- properties: Properties;
- blacklist: string[];
-}
-
-export const keymapsValueOf = (o: any): Keymaps => {
- return Object.keys(o).reduce((keymaps: Keymaps, key: string): Keymaps => {
- let op = operations.valueOf(o[key]);
- keymaps[key] = op;
- return keymaps;
- }, {});
-};
-
-export const searchValueOf = (o: any): Search => {
- if (typeof o.default !== 'string') {
- throw new TypeError('string field "default" not set"');
- }
- for (let name of Object.keys(o.engines)) {
- if ((/\s/).test(name)) {
- throw new TypeError(
- `While space in the search engine not allowed: "${name}"`);
- }
- let url = o.engines[name];
- if (typeof url !== 'string') {
- throw new TypeError('"engines" not an object of string');
- }
- let matches = url.match(/{}/g);
- if (matches === null) {
- throw new TypeError(`No {}-placeholders in URL of "${name}"`);
- } else if (matches.length > 1) {
- throw new TypeError(`Multiple {}-placeholders in URL of "${name}"`);
- }
-
- }
- if (!Object.prototype.hasOwnProperty.call(o.engines, o.default)) {
- throw new TypeError(`Default engine "${o.default}" not found`);
- }
- return {
- default: o.default as string,
- engines: { ...o.engines },
- };
-};
-
-export const propertiesValueOf = (o: any): Properties => {
- let defNames = new Set(PropertyDefs.defs.map(def => def.name));
- let unknownName = Object.keys(o).find(name => !defNames.has(name));
- if (unknownName) {
- throw new TypeError(`Unknown property name: "${unknownName}"`);
- }
-
- for (let def of PropertyDefs.defs) {
- if (!Object.prototype.hasOwnProperty.call(o, def.name)) {
- continue;
- }
- if (typeof o[def.name] !== def.type) {
- throw new TypeError(`property "${def.name}" is not ${def.type}`);
- }
- }
- return {
- ...PropertyDefs.defaultValues,
- ...o,
- };
-};
-
-export const blacklistValueOf = (o: any): string[] => {
- if (!Array.isArray(o)) {
- throw new TypeError(`"blacklist" is not an array of string`);
- }
- for (let x of o) {
- if (typeof x !== 'string') {
- throw new TypeError(`"blacklist" is not an array of string`);
- }
- }
- return o as string[];
-};
-
-export const valueOf = (o: any): Settings => {
- let settings = { ...DefaultSetting };
- for (let key of Object.keys(o)) {
- switch (key) {
- case 'keymaps':
- settings.keymaps = keymapsValueOf(o.keymaps);
- break;
- case 'search':
- settings.search = searchValueOf(o.search);
- break;
- case 'properties':
- settings.properties = propertiesValueOf(o.properties);
- break;
- case 'blacklist':
- settings.blacklist = blacklistValueOf(o.blacklist);
- break;
- default:
- throw new TypeError('unknown setting: ' + key);
- }
- }
- return settings;
-};
-
-export const DefaultSetting: Settings = {
- keymaps: {
- '0': { 'type': 'scroll.home' },
- ':': { 'type': 'command.show' },
- 'o': { 'type': 'command.show.open', 'alter': false },
- 'O': { 'type': 'command.show.open', 'alter': true },
- 't': { 'type': 'command.show.tabopen', 'alter': false },
- 'T': { 'type': 'command.show.tabopen', 'alter': true },
- 'w': { 'type': 'command.show.winopen', 'alter': false },
- 'W': { 'type': 'command.show.winopen', 'alter': true },
- 'b': { 'type': 'command.show.buffer' },
- 'a': { 'type': 'command.show.addbookmark', 'alter': true },
- 'k': { 'type': 'scroll.vertically', 'count': -1 },
- 'j': { 'type': 'scroll.vertically', 'count': 1 },
- 'h': { 'type': 'scroll.horizonally', 'count': -1 },
- 'l': { 'type': 'scroll.horizonally', 'count': 1 },
- '<C-U>': { 'type': 'scroll.pages', 'count': -0.5 },
- '<C-D>': { 'type': 'scroll.pages', 'count': 0.5 },
- '<C-B>': { 'type': 'scroll.pages', 'count': -1 },
- '<C-F>': { 'type': 'scroll.pages', 'count': 1 },
- 'gg': { 'type': 'scroll.top' },
- 'G': { 'type': 'scroll.bottom' },
- '$': { 'type': 'scroll.end' },
- 'd': { 'type': 'tabs.close' },
- 'D': { 'type': 'tabs.close', 'select': 'left' },
- 'x$': { 'type': 'tabs.close.right' },
- '!d': { 'type': 'tabs.close.force' },
- 'u': { 'type': 'tabs.reopen' },
- 'K': { 'type': 'tabs.prev' },
- 'J': { 'type': 'tabs.next' },
- 'gT': { 'type': 'tabs.prev' },
- 'gt': { 'type': 'tabs.next' },
- 'g0': { 'type': 'tabs.first' },
- 'g$': { 'type': 'tabs.last' },
- '<C-6>': { 'type': 'tabs.prevsel' },
- 'r': { 'type': 'tabs.reload', 'cache': false },
- 'R': { 'type': 'tabs.reload', 'cache': true },
- 'zp': { 'type': 'tabs.pin.toggle' },
- 'zd': { 'type': 'tabs.duplicate' },
- 'zi': { 'type': 'zoom.in' },
- 'zo': { 'type': 'zoom.out' },
- 'zz': { 'type': 'zoom.neutral' },
- 'f': { 'type': 'follow.start', 'newTab': false, 'background': false },
- 'F': { 'type': 'follow.start', 'newTab': true, 'background': false },
- 'm': { 'type': 'mark.set.prefix' },
- '\'': { 'type': 'mark.jump.prefix' },
- 'H': { 'type': 'navigate.history.prev' },
- 'L': { 'type': 'navigate.history.next' },
- '[[': { 'type': 'navigate.link.prev' },
- ']]': { 'type': 'navigate.link.next' },
- 'gu': { 'type': 'navigate.parent' },
- 'gU': { 'type': 'navigate.root' },
- 'gi': { 'type': 'focus.input' },
- 'gf': { 'type': 'page.source' },
- 'gh': { 'type': 'page.home', 'newTab': false },
- 'gH': { 'type': 'page.home', 'newTab': true },
- 'y': { 'type': 'urls.yank' },
- 'p': { 'type': 'urls.paste', 'newTab': false },
- 'P': { 'type': 'urls.paste', 'newTab': true },
- '/': { 'type': 'find.start' },
- 'n': { 'type': 'find.next' },
- 'N': { 'type': 'find.prev' },
- '.': { 'type': 'repeat.last' },
- '<S-Esc>': { 'type': 'addon.toggle.enabled' }
- },
- search: {
- default: 'google',
- engines: {
- 'google': 'https://google.com/search?q={}',
- 'yahoo': 'https://search.yahoo.com/search?p={}',
- 'bing': 'https://www.bing.com/search?q={}',
- 'duckduckgo': 'https://duckduckgo.com/?q={}',
- 'twitter': 'https://twitter.com/search?q={}',
- 'wikipedia': 'https://en.wikipedia.org/w/index.php?search={}'
- }
- },
- properties: {
- hintchars: 'abcdefghijklmnopqrstuvwxyz',
- smoothscroll: false,
- complete: 'sbh'
- },
- blacklist: []
-};
diff --git a/src/shared/blacklists.ts b/src/shared/blacklists.ts
deleted file mode 100644
index 61ee4de..0000000
--- a/src/shared/blacklists.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import * as re from './utils/re';
-
-const includes = (blacklist: string[], url: string): boolean => {
- let u = new URL(url);
- return blacklist.some((item) => {
- if (!item.includes('/')) {
- return re.fromWildcard(item).test(u.host);
- }
- return re.fromWildcard(item).test(u.host + u.pathname);
- });
-};
-
-export { includes };
diff --git a/src/shared/properties.ts b/src/shared/properties.ts
deleted file mode 100644
index 6315030..0000000
--- a/src/shared/properties.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-export type Type = string | number | boolean;
-
-export class Def {
- private name0: string;
-
- private description0: string;
-
- private defaultValue0: Type;
-
- constructor(
- name: string,
- description: string,
- defaultValue: Type,
- ) {
- this.name0 = name;
- this.description0 = description;
- this.defaultValue0 = defaultValue;
- }
-
- public get name(): string {
- return this.name0;
- }
-
- public get defaultValue(): Type {
- return this.defaultValue0;
- }
-
- public get description(): Type {
- return this.description0;
- }
-
- public get type(): string {
- return typeof this.defaultValue;
- }
-}
-
-export const defs: Def[] = [
- new Def(
- 'hintchars',
- 'hint characters on follow mode',
- 'abcdefghijklmnopqrstuvwxyz'),
- new Def(
- 'smoothscroll',
- 'smooth scroll',
- false),
- new Def(
- 'complete',
- 'which are completed at the open page',
- 'sbh'),
-];
diff --git a/src/shared/property-defs.ts b/src/shared/property-defs.ts
deleted file mode 100644
index fec9f80..0000000
--- a/src/shared/property-defs.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-export type Type = string | number | boolean;
-
-export class Def {
- private name0: string;
-
- private description0: string;
-
- private defaultValue0: Type;
-
- constructor(
- name: string,
- description: string,
- defaultValue: Type,
- ) {
- this.name0 = name;
- this.description0 = description;
- this.defaultValue0 = defaultValue;
- }
-
- public get name(): string {
- return this.name0;
- }
-
- public get defaultValue(): Type {
- return this.defaultValue0;
- }
-
- public get description(): Type {
- return this.description0;
- }
-
- public get type(): string {
- return typeof this.defaultValue;
- }
-}
-
-export const defs: Def[] = [
- new Def(
- 'hintchars',
- 'hint characters on follow mode',
- 'abcdefghijklmnopqrstuvwxyz'),
- new Def(
- 'smoothscroll',
- 'smooth scroll',
- false),
- new Def(
- 'complete',
- 'which are completed at the open page',
- 'sbh'),
-];
-
-export const defaultValues = {
- hintchars: 'abcdefghijklmnopqrstuvwxyz',
- smoothscroll: false,
- complete: 'sbh',
-};
diff --git a/src/shared/settings/Blacklist.ts b/src/shared/settings/Blacklist.ts
new file mode 100644
index 0000000..a95b606
--- /dev/null
+++ b/src/shared/settings/Blacklist.ts
@@ -0,0 +1,39 @@
+export type BlacklistJSON = string[];
+
+const fromWildcard = (pattern: string): RegExp => {
+ let regexStr = '^' + pattern.replace(/\*/g, '.*') + '$';
+ return new RegExp(regexStr);
+};
+
+export default class Blacklist {
+ constructor(
+ private blacklist: string[],
+ ) {
+ }
+
+ static fromJSON(json: any): Blacklist {
+ if (!Array.isArray(json)) {
+ throw new TypeError(`"blacklist" is not an array of string`);
+ }
+ for (let x of json) {
+ if (typeof x !== 'string') {
+ throw new TypeError(`"blacklist" is not an array of string`);
+ }
+ }
+ return new Blacklist(json);
+ }
+
+ toJSON(): BlacklistJSON {
+ return this.blacklist;
+ }
+
+ includes(url: string): boolean {
+ let u = new URL(url);
+ return this.blacklist.some((item) => {
+ if (!item.includes('/')) {
+ return fromWildcard(item).test(u.host);
+ }
+ return fromWildcard(item).test(u.host + u.pathname);
+ });
+ }
+}
diff --git a/src/shared/settings/Key.ts b/src/shared/settings/Key.ts
new file mode 100644
index 0000000..b11eeb2
--- /dev/null
+++ b/src/shared/settings/Key.ts
@@ -0,0 +1,61 @@
+export default class Key {
+ public readonly key: string;
+
+ public readonly shift: boolean;
+
+ public readonly ctrl: boolean;
+
+ public readonly alt: boolean;
+
+ public readonly meta: boolean;
+
+ constructor({ key, shift, ctrl, alt, meta }: {
+ key: string;
+ shift: boolean;
+ ctrl: boolean;
+ alt: boolean;
+ meta: boolean;
+ }) {
+ this.key = key;
+ this.shift = shift;
+ this.ctrl = ctrl;
+ this.alt = alt;
+ this.meta = meta;
+ }
+
+ static fromMapKey(str: string): Key {
+ if (str.startsWith('<') && str.endsWith('>')) {
+ let inner = str.slice(1, -1);
+ let shift = inner.includes('S-');
+ let base = inner.slice(inner.lastIndexOf('-') + 1);
+ if (shift && base.length === 1) {
+ base = base.toUpperCase();
+ } else if (!shift && base.length === 1) {
+ base = base.toLowerCase();
+ }
+ return new Key({
+ key: base,
+ shift: shift,
+ ctrl: inner.includes('C-'),
+ alt: inner.includes('A-'),
+ meta: inner.includes('M-'),
+ });
+ }
+
+ return new Key({
+ key: str,
+ shift: str.toLowerCase() !== str,
+ ctrl: false,
+ alt: false,
+ meta: false,
+ });
+ }
+
+ equals(key: Key) {
+ return this.key === key.key &&
+ this.ctrl === key.ctrl &&
+ this.meta === key.meta &&
+ this.alt === key.alt &&
+ this.shift === key.shift;
+ }
+}
diff --git a/src/shared/settings/KeySequence.ts b/src/shared/settings/KeySequence.ts
new file mode 100644
index 0000000..4955583
--- /dev/null
+++ b/src/shared/settings/KeySequence.ts
@@ -0,0 +1,54 @@
+import Key from '../../shared/settings/Key';
+
+export default class KeySequence {
+ constructor(
+ public readonly keys: Key[],
+ ) {
+ }
+
+ push(key: Key): number {
+ return this.keys.push(key);
+ }
+
+ length(): number {
+ return this.keys.length;
+ }
+
+ startsWith(o: KeySequence): boolean {
+ if (this.keys.length < o.keys.length) {
+ return false;
+ }
+ for (let i = 0; i < o.keys.length; ++i) {
+ if (!this.keys[i].equals(o.keys[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static fromMapKeys(keys: string): KeySequence {
+ const fromMapKeysRecursive = (
+ remaining: string, mappedKeys: Key[],
+ ): Key[] => {
+ if (remaining.length === 0) {
+ return mappedKeys;
+ }
+
+ let nextPos = 1;
+ if (remaining.startsWith('<')) {
+ let ltPos = remaining.indexOf('>');
+ if (ltPos > 0) {
+ nextPos = ltPos + 1;
+ }
+ }
+
+ return fromMapKeysRecursive(
+ remaining.slice(nextPos),
+ mappedKeys.concat([Key.fromMapKey(remaining.slice(0, nextPos))])
+ );
+ };
+
+ let data = fromMapKeysRecursive(keys, []);
+ return new KeySequence(data);
+ }
+}
diff --git a/src/shared/settings/Keymaps.ts b/src/shared/settings/Keymaps.ts
new file mode 100644
index 0000000..a5558b0
--- /dev/null
+++ b/src/shared/settings/Keymaps.ts
@@ -0,0 +1,37 @@
+import * as operations from '../operations';
+
+export type KeymapsJSON = { [key: string]: operations.Operation };
+
+export default class Keymaps {
+ constructor(
+ private readonly data: KeymapsJSON,
+ ) {
+ }
+
+ static fromJSON(json: any): Keymaps {
+ if (typeof json !== 'object' || json === null) {
+ throw new TypeError('invalid keymaps type: ' + JSON.stringify(json));
+ }
+
+ let data: KeymapsJSON = {};
+ for (let key of Object.keys(json)) {
+ data[key] = operations.valueOf(json[key]);
+ }
+ return new Keymaps(data);
+ }
+
+ combine(other: Keymaps): Keymaps {
+ return new Keymaps({
+ ...this.data,
+ ...other.data,
+ });
+ }
+
+ toJSON(): KeymapsJSON {
+ return this.data;
+ }
+
+ entries(): [string, operations.Operation][] {
+ return Object.entries(this.data);
+ }
+}
diff --git a/src/shared/settings/Properties.ts b/src/shared/settings/Properties.ts
new file mode 100644
index 0000000..63ff991
--- /dev/null
+++ b/src/shared/settings/Properties.ts
@@ -0,0 +1,110 @@
+export type PropertiesJSON = {
+ hintchars?: string;
+ smoothscroll?: boolean;
+ complete?: string;
+};
+
+export type PropertyTypes = {
+ hintchars: string;
+ smoothscroll: string;
+ complete: string;
+};
+
+type PropertyName = 'hintchars' | 'smoothscroll' | 'complete';
+
+type PropertyDef = {
+ name: PropertyName;
+ description: string;
+ defaultValue: string | number | boolean;
+ type: 'string' | 'number' | 'boolean';
+};
+
+const defs: PropertyDef[] = [
+ {
+ name: 'hintchars',
+ description: 'hint characters on follow mode',
+ defaultValue: 'abcdefghijklmnopqrstuvwxyz',
+ type: 'string',
+ }, {
+ name: 'smoothscroll',
+ description: 'smooth scroll',
+ defaultValue: false,
+ type: 'boolean',
+ }, {
+ name: 'complete',
+ description: 'which are completed at the open page',
+ defaultValue: 'sbh',
+ type: 'string',
+ }
+];
+
+const defaultValues = {
+ hintchars: 'abcdefghijklmnopqrstuvwxyz',
+ smoothscroll: false,
+ complete: 'sbh',
+};
+
+export default class Properties {
+ public hintchars: string;
+
+ public smoothscroll: boolean;
+
+ public complete: string;
+
+ constructor({
+ hintchars,
+ smoothscroll,
+ complete,
+ }: {
+ hintchars?: string;
+ smoothscroll?: boolean;
+ complete?: string;
+ } = {}) {
+ this.hintchars = hintchars || defaultValues.hintchars;
+ this.smoothscroll = smoothscroll || defaultValues.smoothscroll;
+ this.complete = complete || defaultValues.complete;
+ }
+
+ static fromJSON(json: any): Properties {
+ let defNames: Set<string> = new Set(defs.map(def => def.name));
+ let unknownName = Object.keys(json).find(name => !defNames.has(name));
+ if (unknownName) {
+ throw new TypeError(`Unknown property name: "${unknownName}"`);
+ }
+
+ for (let def of defs) {
+ if (!Object.prototype.hasOwnProperty.call(json, def.name)) {
+ continue;
+ }
+ if (typeof json[def.name] !== def.type) {
+ throw new TypeError(
+ `property "${def.name}" is not ${def.type}`);
+ }
+ }
+ return new Properties(json);
+ }
+
+ static types(): PropertyTypes {
+ return {
+ hintchars: 'string',
+ smoothscroll: 'boolean',
+ complete: 'string',
+ };
+ }
+
+ static def(name: string): PropertyDef | undefined {
+ return defs.find(p => p.name === name);
+ }
+
+ static defs(): PropertyDef[] {
+ return defs;
+ }
+
+ toJSON(): PropertiesJSON {
+ return {
+ hintchars: this.hintchars,
+ smoothscroll: this.smoothscroll,
+ complete: this.complete,
+ };
+ }
+}
diff --git a/src/shared/settings/Search.ts b/src/shared/settings/Search.ts
new file mode 100644
index 0000000..4580236
--- /dev/null
+++ b/src/shared/settings/Search.ts
@@ -0,0 +1,76 @@
+type Entries = { [name: string]: string };
+
+export type SearchJSON = {
+ default: string;
+ engines: { [key: string]: string };
+};
+
+export default class Search {
+ constructor(
+ public defaultEngine: string,
+ public engines: Entries,
+ ) {
+ }
+
+ static fromJSON(json: any): Search {
+ let defaultEngine = Search.getStringField(json, 'default');
+ let engines = Search.getObjectField(json, 'engines');
+
+ for (let [name, url] of Object.entries(engines)) {
+ if ((/\s/).test(name)) {
+ throw new TypeError(
+ `While space in the search engine not allowed: "${name}"`);
+ }
+ if (typeof url !== 'string') {
+ throw new TypeError(
+ `Invalid type of value in filed "engines": ${JSON.stringify(json)}`);
+ }
+ let matches = url.match(/{}/g);
+ if (matches === null) {
+ throw new TypeError(`No {}-placeholders in URL of "${name}"`);
+ } else if (matches.length > 1) {
+ throw new TypeError(`Multiple {}-placeholders in URL of "${name}"`);
+ }
+ }
+
+ if (!Object.keys(engines).includes(defaultEngine)) {
+ throw new TypeError(`Default engine "${defaultEngine}" not found`);
+ }
+
+ return new Search(
+ json.default as string,
+ json.engines,
+ );
+ }
+
+ toJSON(): SearchJSON {
+ return {
+ default: this.defaultEngine,
+ engines: this.engines,
+ };
+ }
+
+ private static getStringField(json: any, name: string): string {
+ if (!Object.prototype.hasOwnProperty.call(json, name)) {
+ throw new TypeError(
+ `missing field "${name}" on search: ${JSON.stringify(json)}`);
+ }
+ if (typeof json[name] !== 'string') {
+ throw new TypeError(
+ `invalid type of filed "${name}" on search: ${JSON.stringify(json)}`);
+ }
+ return json[name];
+ }
+
+ private static getObjectField(json: any, name: string): Object {
+ if (!Object.prototype.hasOwnProperty.call(json, name)) {
+ throw new TypeError(
+ `missing field "${name}" on search: ${JSON.stringify(json)}`);
+ }
+ if (typeof json[name] !== 'object' || json[name] === null) {
+ throw new TypeError(
+ `invalid type of filed "${name}" on search: ${JSON.stringify(json)}`);
+ }
+ return json[name];
+ }
+}
diff --git a/src/shared/settings/Settings.ts b/src/shared/settings/Settings.ts
new file mode 100644
index 0000000..2c9e37f
--- /dev/null
+++ b/src/shared/settings/Settings.ts
@@ -0,0 +1,158 @@
+import Keymaps, { KeymapsJSON } from './Keymaps';
+import Search, { SearchJSON } from './Search';
+import Properties, { PropertiesJSON } from './Properties';
+import Blacklist, { BlacklistJSON } from './Blacklist';
+
+export type SettingsJSON = {
+ keymaps: KeymapsJSON,
+ search: SearchJSON,
+ properties: PropertiesJSON,
+ blacklist: BlacklistJSON,
+};
+
+export default class Settings {
+ public keymaps: Keymaps;
+
+ public search: Search;
+
+ public properties: Properties;
+
+ public blacklist: Blacklist;
+
+ constructor({
+ keymaps,
+ search,
+ properties,
+ blacklist,
+ }: {
+ keymaps: Keymaps;
+ search: Search;
+ properties: Properties;
+ blacklist: Blacklist;
+ }) {
+ this.keymaps = keymaps;
+ this.search = search;
+ this.properties = properties;
+ this.blacklist = blacklist;
+ }
+
+ static fromJSON(json: any): Settings {
+ let settings = { ...DefaultSetting };
+ for (let key of Object.keys(json)) {
+ switch (key) {
+ case 'keymaps':
+ settings.keymaps = Keymaps.fromJSON(json.keymaps);
+ break;
+ case 'search':
+ settings.search = Search.fromJSON(json.search);
+ break;
+ case 'properties':
+ settings.properties = Properties.fromJSON(json.properties);
+ break;
+ case 'blacklist':
+ settings.blacklist = Blacklist.fromJSON(json.blacklist);
+ break;
+ default:
+ throw new TypeError('unknown setting: ' + key);
+ }
+ }
+ return new Settings(settings);
+ }
+
+ toJSON(): SettingsJSON {
+ return {
+ keymaps: this.keymaps.toJSON(),
+ search: this.search.toJSON(),
+ properties: this.properties.toJSON(),
+ blacklist: this.blacklist.toJSON(),
+ };
+ }
+}
+
+export const DefaultSettingJSONText = `{
+ "keymaps": {
+ "0": { "type": "scroll.home" },
+ ":": { "type": "command.show" },
+ "o": { "type": "command.show.open", "alter": false },
+ "O": { "type": "command.show.open", "alter": true },
+ "t": { "type": "command.show.tabopen", "alter": false },
+ "T": { "type": "command.show.tabopen", "alter": true },
+ "w": { "type": "command.show.winopen", "alter": false },
+ "W": { "type": "command.show.winopen", "alter": true },
+ "b": { "type": "command.show.buffer" },
+ "a": { "type": "command.show.addbookmark", "alter": true },
+ "k": { "type": "scroll.vertically", "count": -1 },
+ "j": { "type": "scroll.vertically", "count": 1 },
+ "h": { "type": "scroll.horizonally", "count": -1 },
+ "l": { "type": "scroll.horizonally", "count": 1 },
+ "<C-U>": { "type": "scroll.pages", "count": -0.5 },
+ "<C-D>": { "type": "scroll.pages", "count": 0.5 },
+ "<C-B>": { "type": "scroll.pages", "count": -1 },
+ "<C-F>": { "type": "scroll.pages", "count": 1 },
+ "gg": { "type": "scroll.top" },
+ "G": { "type": "scroll.bottom" },
+ "$": { "type": "scroll.end" },
+ "d": { "type": "tabs.close" },
+ "D": { "type": "tabs.close", "select": "left" },
+ "x$": { "type": "tabs.close.right" },
+ "!d": { "type": "tabs.close.force" },
+ "u": { "type": "tabs.reopen" },
+ "K": { "type": "tabs.prev" },
+ "J": { "type": "tabs.next" },
+ "gT": { "type": "tabs.prev" },
+ "gt": { "type": "tabs.next" },
+ "g0": { "type": "tabs.first" },
+ "g$": { "type": "tabs.last" },
+ "<C-6>": { "type": "tabs.prevsel" },
+ "r": { "type": "tabs.reload", "cache": false },
+ "R": { "type": "tabs.reload", "cache": true },
+ "zp": { "type": "tabs.pin.toggle" },
+ "zd": { "type": "tabs.duplicate" },
+ "zi": { "type": "zoom.in" },
+ "zo": { "type": "zoom.out" },
+ "zz": { "type": "zoom.neutral" },
+ "f": { "type": "follow.start", "newTab": false },
+ "F": { "type": "follow.start", "newTab": true, "background": false },
+ "m": { "type": "mark.set.prefix" },
+ "'": { "type": "mark.jump.prefix" },
+ "H": { "type": "navigate.history.prev" },
+ "L": { "type": "navigate.history.next" },
+ "[[": { "type": "navigate.link.prev" },
+ "]]": { "type": "navigate.link.next" },
+ "gu": { "type": "navigate.parent" },
+ "gU": { "type": "navigate.root" },
+ "gi": { "type": "focus.input" },
+ "gf": { "type": "page.source" },
+ "gh": { "type": "page.home" },
+ "gH": { "type": "page.home", "newTab": true },
+ "y": { "type": "urls.yank" },
+ "p": { "type": "urls.paste", "newTab": false },
+ "P": { "type": "urls.paste", "newTab": true },
+ "/": { "type": "find.start" },
+ "n": { "type": "find.next" },
+ "N": { "type": "find.prev" },
+ ".": { "type": "repeat.last" },
+ "<S-Esc>": { "type": "addon.toggle.enabled" }
+ },
+ "search": {
+ "default": "google",
+ "engines": {
+ "google": "https://google.com/search?q={}",
+ "yahoo": "https://search.yahoo.com/search?p={}",
+ "bing": "https://www.bing.com/search?q={}",
+ "duckduckgo": "https://duckduckgo.com/?q={}",
+ "twitter": "https://twitter.com/search?q={}",
+ "wikipedia": "https://en.wikipedia.org/w/index.php?search={}"
+ }
+ },
+ "properties": {
+ "hintchars": "abcdefghijklmnopqrstuvwxyz",
+ "smoothscroll": false,
+ "complete": "sbh"
+ },
+ "blacklist": [
+ ]
+}`;
+
+export const DefaultSetting: Settings =
+ Settings.fromJSON(JSON.parse(DefaultSettingJSONText));
diff --git a/src/shared/urls.ts b/src/shared/urls.ts
index bbdb1ea..64ea4f2 100644
--- a/src/shared/urls.ts
+++ b/src/shared/urls.ts
@@ -1,4 +1,4 @@
-import { Search } from './Settings';
+import Search from './settings/Search';
const trimStart = (str: string): string => {
// NOTE String.trimStart is available on Firefox 61
@@ -19,7 +19,7 @@ const searchUrl = (keywords: string, search: Search): string => {
if (keywords.includes('.') && !keywords.includes(' ')) {
return 'http://' + keywords;
}
- let template = search.engines[search.default];
+ let template = search.engines[search.defaultEngine];
let query = keywords;
let first = trimStart(keywords).split(' ')[0];
diff --git a/src/shared/utils/re.ts b/src/shared/utils/re.ts
deleted file mode 100644
index 34f4fa6..0000000
--- a/src/shared/utils/re.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-const fromWildcard = (pattern: string): RegExp => {
- let regexStr = '^' + pattern.replace(/\*/g, '.*') + '$';
- return new RegExp(regexStr);
-};
-
-export { fromWildcard };
diff --git a/test/content/InputDriver.test.ts b/test/content/InputDriver.test.ts
index b9f2c28..441d107 100644
--- a/test/content/InputDriver.test.ts
+++ b/test/content/InputDriver.test.ts
@@ -1,6 +1,6 @@
-import InputDriver from '../../src/content/InputDriver';
+import InputDriver, {keyFromKeyboardEvent} from '../../src/content/InputDriver';
import { expect } from 'chai';
-import Key from '../../src/content/domains/Key';
+import Key from '../../src/shared/settings/Key';
describe('InputDriver', () => {
let target: HTMLElement;
@@ -21,10 +21,10 @@ describe('InputDriver', () => {
it('register callbacks', (done) => {
driver.onKey((key: Key): boolean => {
expect(key.key).to.equal('a');
- expect(key.ctrlKey).to.be.true;
- expect(key.shiftKey).to.be.false;
- expect(key.altKey).to.be.false;
- expect(key.metaKey).to.be.false;
+ expect(key.ctrl).to.be.true;
+ expect(key.shift).to.be.false;
+ expect(key.alt).to.be.false;
+ expect(key.meta).to.be.false;
done();
return true;
});
@@ -68,15 +68,15 @@ describe('InputDriver', () => {
it('propagates and stop handler chain', () => {
let a = 0, b = 0, c = 0;
- driver.onKey((key: Key): boolean => {
+ driver.onKey((_key: Key): boolean => {
a++;
return false;
});
- driver.onKey((key: Key): boolean => {
+ driver.onKey((_key: Key): boolean => {
b++;
return true;
});
- driver.onKey((key: Key): boolean => {
+ driver.onKey((_key: Key): boolean => {
c++;
return true;
});
@@ -89,7 +89,7 @@ describe('InputDriver', () => {
})
it('does not invoke only meta keys', () => {
- driver.onKey((key: Key): boolean=> {
+ driver.onKey((_key: Key): boolean=> {
expect.fail();
return false;
});
@@ -115,7 +115,7 @@ describe('InputDriver', () => {
it('ignores events from contenteditable elements', () => {
let div = window.document.createElement('div');
let driver = new InputDriver(div);
- driver.onKey((key: Key): boolean => {
+ driver.onKey((_key: Key): boolean => {
expect.fail();
return false;
});
@@ -127,3 +127,50 @@ describe('InputDriver', () => {
div.dispatchEvent(new KeyboardEvent('keydown', { key: 'x' }));
});
});
+
+describe("#keyFromKeyboardEvent", () => {
+ it('returns from keyboard input Ctrl+X', () => {
+ let k = keyFromKeyboardEvent(new KeyboardEvent('keydown', {
+ key: 'x', shiftKey: false, ctrlKey: true, altKey: false, metaKey: true,
+ }));
+ expect(k.key).to.equal('x');
+ expect(k.shift).to.be.false;
+ expect(k.ctrl).to.be.true;
+ expect(k.alt).to.be.false;
+ expect(k.meta).to.be.true;
+ });
+
+ it('returns from keyboard input Shift+Esc', () => {
+ let k = keyFromKeyboardEvent(new KeyboardEvent('keydown', {
+ key: 'Escape', shiftKey: true, ctrlKey: false, altKey: false, metaKey: true
+ }));
+ expect(k.key).to.equal('Esc');
+ expect(k.shift).to.be.true;
+ expect(k.ctrl).to.be.false;
+ expect(k.alt).to.be.false;
+ expect(k.meta).to.be.true;
+ });
+
+ it('returns from keyboard input Ctrl+$', () => {
+ // $ required shift pressing on most keyboards
+ let k = keyFromKeyboardEvent(new KeyboardEvent('keydown', {
+ key: '$', shiftKey: true, ctrlKey: true, altKey: false, metaKey: false
+ }));
+ expect(k.key).to.equal('$');
+ expect(k.shift).to.be.false;
+ expect(k.ctrl).to.be.true;
+ expect(k.alt).to.be.false;
+ expect(k.meta).to.be.false;
+ });
+
+ it('returns from keyboard input Crtl+Space', () => {
+ let k = keyFromKeyboardEvent(new KeyboardEvent('keydown', {
+ key: ' ', shiftKey: false, ctrlKey: true, altKey: false, metaKey: false
+ }));
+ expect(k.key).to.equal('Space');
+ expect(k.shift).to.be.false;
+ expect(k.ctrl).to.be.true;
+ expect(k.alt).to.be.false;
+ expect(k.meta).to.be.false;
+ });
+});
diff --git a/test/content/domains/Key.test.ts b/test/content/domains/Key.test.ts
deleted file mode 100644
index b3f9fb6..0000000
--- a/test/content/domains/Key.test.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-import Key, * as keys from '../../../src/content/domains/Key';
-import { expect } from 'chai'
-
-describe("Key", () => {
- describe('fromKeyboardEvent', () => {
- it('returns from keyboard input Ctrl+X', () => {
- let k = keys.fromKeyboardEvent(new KeyboardEvent('keydown', {
- key: 'x', shiftKey: false, ctrlKey: true, altKey: false, metaKey: true,
- }));
- expect(k.key).to.equal('x');
- expect(k.shiftKey).to.be.false;
- expect(k.ctrlKey).to.be.true;
- expect(k.altKey).to.be.false;
- expect(k.metaKey).to.be.true;
- });
-
- it('returns from keyboard input Shift+Esc', () => {
- let k = keys.fromKeyboardEvent(new KeyboardEvent('keydown', {
- key: 'Escape', shiftKey: true, ctrlKey: false, altKey: false, metaKey: true
- }));
- expect(k.key).to.equal('Esc');
- expect(k.shiftKey).to.be.true;
- expect(k.ctrlKey).to.be.false;
- expect(k.altKey).to.be.false;
- expect(k.metaKey).to.be.true;
- });
-
- it('returns from keyboard input Ctrl+$', () => {
- // $ required shift pressing on most keyboards
- let k = keys.fromKeyboardEvent(new KeyboardEvent('keydown', {
- key: '$', shiftKey: true, ctrlKey: true, altKey: false, metaKey: false
- }));
- expect(k.key).to.equal('$');
- expect(k.shiftKey).to.be.false;
- expect(k.ctrlKey).to.be.true;
- expect(k.altKey).to.be.false;
- expect(k.metaKey).to.be.false;
- });
-
- it('returns from keyboard input Crtl+Space', () => {
- let k = keys.fromKeyboardEvent(new KeyboardEvent('keydown', {
- key: ' ', shiftKey: false, ctrlKey: true, altKey: false, metaKey: false
- }));
- expect(k.key).to.equal('Space');
- expect(k.shiftKey).to.be.false;
- expect(k.ctrlKey).to.be.true;
- expect(k.altKey).to.be.false;
- expect(k.metaKey).to.be.false;
- });
- });
-
- describe('fromMapKey', () => {
- it('return for X', () => {
- let key = keys.fromMapKey('x');
- expect(key.key).to.equal('x');
- expect(key.shiftKey).to.be.false;
- expect(key.ctrlKey).to.be.false;
- expect(key.altKey).to.be.false;
- expect(key.metaKey).to.be.false;
- });
-
- it('return for Shift+X', () => {
- let key = keys.fromMapKey('X');
- expect(key.key).to.equal('X');
- expect(key.shiftKey).to.be.true;
- expect(key.ctrlKey).to.be.false;
- expect(key.altKey).to.be.false;
- expect(key.metaKey).to.be.false;
- });
-
- it('return for Ctrl+X', () => {
- let key = keys.fromMapKey('<C-X>');
- expect(key.key).to.equal('x');
- expect(key.shiftKey).to.be.false;
- expect(key.ctrlKey).to.be.true;
- expect(key.altKey).to.be.false;
- expect(key.metaKey).to.be.false;
- });
-
- it('returns for Ctrl+Meta+X', () => {
- let key = keys.fromMapKey('<C-M-X>');
- expect(key.key).to.equal('x');
- expect(key.shiftKey).to.be.false;
- expect(key.ctrlKey).to.be.true;
- expect(key.altKey).to.be.false;
- expect(key.metaKey).to.be.true;
- });
-
- it('returns for Ctrl+Shift+x', () => {
- let key = keys.fromMapKey('<C-S-x>');
- expect(key.key).to.equal('X');
- expect(key.shiftKey).to.be.true;
- expect(key.ctrlKey).to.be.true;
- expect(key.altKey).to.be.false;
- expect(key.metaKey).to.be.false;
- });
-
- it('returns for Shift+Esc', () => {
- let key = keys.fromMapKey('<S-Esc>');
- expect(key.key).to.equal('Esc');
- expect(key.shiftKey).to.be.true;
- expect(key.ctrlKey).to.be.false;
- expect(key.altKey).to.be.false;
- expect(key.metaKey).to.be.false;
- });
-
- it('returns for Ctrl+Esc', () => {
- let key = keys.fromMapKey('<C-Esc>');
- expect(key.key).to.equal('Esc');
- expect(key.shiftKey).to.be.false;
- expect(key.ctrlKey).to.be.true;
- expect(key.altKey).to.be.false;
- expect(key.metaKey).to.be.false;
- });
-
- it('returns for Ctrl+Esc', () => {
- let key = keys.fromMapKey('<C-Space>');
- expect(key.key).to.equal('Space');
- expect(key.shiftKey).to.be.false;
- expect(key.ctrlKey).to.be.true;
- expect(key.altKey).to.be.false;
- expect(key.metaKey).to.be.false;
- });
- });
-
- describe('equals', () => {
- expect(keys.equals(
- { key: 'x', ctrlKey: true, },
- { key: 'x', ctrlKey: true, },
- )).to.be.true;
-
- expect(keys.equals(
- { key: 'X', shiftKey: true, },
- { key: 'x', ctrlKey: true, },
- )).to.be.false;
- });
-});
diff --git a/test/content/domains/KeySequence.test.ts b/test/content/domains/KeySequence.test.ts
deleted file mode 100644
index 7387c06..0000000
--- a/test/content/domains/KeySequence.test.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import KeySequence, * as utils from '../../../src/content/domains/KeySequence';
-import { expect } from 'chai'
-
-describe("KeySequence", () => {
- describe('#push', () => {
- it('append a key to the sequence', () => {
- let seq = KeySequence.from([]);
- seq.push({ key: 'g' });
- seq.push({ key: 'u', shiftKey: true });
-
- let array = seq.getKeyArray();
- expect(array[0]).to.deep.equal({ key: 'g' });
- expect(array[1]).to.deep.equal({ key: 'u', shiftKey: true });
- })
- });
-
- describe('#startsWith', () => {
- it('returns true if the key sequence starts with param', () => {
- let seq = KeySequence.from([
- { key: 'g' },
- { key: 'u', shiftKey: true },
- ]);
-
- expect(seq.startsWith(KeySequence.from([
- ]))).to.be.true;
- expect(seq.startsWith(KeySequence.from([
- { key: 'g' },
- ]))).to.be.true;
- expect(seq.startsWith(KeySequence.from([
- { key: 'g' }, { key: 'u', shiftKey: true },
- ]))).to.be.true;
- expect(seq.startsWith(KeySequence.from([
- { key: 'g' }, { key: 'u', shiftKey: true }, { key: 'x' },
- ]))).to.be.false;
- expect(seq.startsWith(KeySequence.from([
- { key: 'h' },
- ]))).to.be.false;
- })
-
- it('returns true if the empty sequence starts with an empty sequence', () => {
- let seq = KeySequence.from([]);
-
- expect(seq.startsWith(KeySequence.from([]))).to.be.true;
- expect(seq.startsWith(KeySequence.from([
- { key: 'h' },
- ]))).to.be.false;
- })
- });
-
- describe('#fromMapKeys', () => {
- it('returns mapped keys for Shift+Esc', () => {
- let keyArray = utils.fromMapKeys('<S-Esc>').getKeyArray();
- expect(keyArray).to.have.lengthOf(1);
- expect(keyArray[0].key).to.equal('Esc');
- expect(keyArray[0].shiftKey).to.be.true;
- });
-
- it('returns mapped keys for a<C-B><A-C>d<M-e>', () => {
- let keyArray = utils.fromMapKeys('a<C-B><A-C>d<M-e>').getKeyArray();
- expect(keyArray).to.have.lengthOf(5);
- expect(keyArray[0].key).to.equal('a');
- expect(keyArray[1].ctrlKey).to.be.true;
- expect(keyArray[1].key).to.equal('b');
- expect(keyArray[2].altKey).to.be.true;
- expect(keyArray[2].key).to.equal('c');
- expect(keyArray[3].key).to.equal('d');
- expect(keyArray[4].metaKey).to.be.true;
- expect(keyArray[4].key).to.equal('e');
- });
- })
-
-});
diff --git a/test/content/repositories/KeymapRepository.test.ts b/test/content/repositories/KeymapRepository.test.ts
index 34704d9..df013df 100644
--- a/test/content/repositories/KeymapRepository.test.ts
+++ b/test/content/repositories/KeymapRepository.test.ts
@@ -1,6 +1,7 @@
import KeymapRepository, { KeymapRepositoryImpl }
from '../../../src/content/repositories/KeymapRepository';
import { expect } from 'chai';
+import Key from "../../../src/shared/settings/Key";
describe('KeymapRepositoryImpl', () => {
let sut: KeymapRepository;
@@ -11,24 +12,25 @@ describe('KeymapRepositoryImpl', () => {
describe('#enqueueKey()', () => {
it('enqueues keys', () => {
- sut.enqueueKey({ key: 'a' });
- sut.enqueueKey({ key: 'b' });
- let sequence = sut.enqueueKey({ key: 'c' });
-
- expect(sequence.getKeyArray()).deep.equals([
- { key: 'a' }, { key: 'b' }, { key: 'c' },
- ]);
+ sut.enqueueKey(Key.fromMapKey('a');
+ sut.enqueueKey(Key.fromMapKey('b');
+ let sequence = sut.enqueueKey(Key.fromMapKey('c'));
+
+ let keys = sequence.keys;
+ expect(keys[0].equals(Key.fromMapKey('a'))).to.be.true;
+ expect(keys[1].equals(Key.fromMapKey('b'))).to.be.true;
+ expect(keys[2].equals(Key.fromMapKey('c'))).to.be.true;
});
});
describe('#clear()', () => {
it('clears keys', () => {
- sut.enqueueKey({ key: 'a' });
- sut.enqueueKey({ key: 'b' });
- sut.enqueueKey({ key: 'c' });
+ sut.enqueueKey(Key.fromMapKey('a'));
+ sut.enqueueKey(Key.fromMapKey('b'));
+ sut.enqueueKey(Key.fromMapKey('c'));
sut.clear();
- let sequence = sut.enqueueKey({ key: 'a' });
+ let sequence = sut.enqueueKey(Key.fromMapKey('a'));
expect(sequence.length()).to.equal(1);
});
});
diff --git a/test/content/repositories/SettingRepository.test.ts b/test/content/repositories/SettingRepository.test.ts
index fea70b7..db4c528 100644
--- a/test/content/repositories/SettingRepository.test.ts
+++ b/test/content/repositories/SettingRepository.test.ts
@@ -1,13 +1,14 @@
import { SettingRepositoryImpl } from '../../../src/content/repositories/SettingRepository';
import { expect } from 'chai';
+import Settings from '../../../src/shared/settings/Settings';
describe('SettingRepositoryImpl', () => {
it('updates and gets current value', () => {
let sut = new SettingRepositoryImpl();
- let settings = {
+ let settings = Settings.fromJSON({
keymaps: {},
- search: {
+ search:{
default: 'google',
engines: {
google: 'https://google.com/?q={}',
@@ -19,7 +20,7 @@ describe('SettingRepositoryImpl', () => {
complete: 'sbh',
},
blacklist: [],
- }
+ });
sut.set(settings);
@@ -27,4 +28,3 @@ describe('SettingRepositoryImpl', () => {
expect(actual.properties.hintchars).to.equal('abcd1234');
});
});
-
diff --git a/test/content/usecases/SettingUseCaase.test.ts b/test/content/usecases/SettingUseCaase.test.ts
index e9633f4..136c5af 100644
--- a/test/content/usecases/SettingUseCaase.test.ts
+++ b/test/content/usecases/SettingUseCaase.test.ts
@@ -1,7 +1,7 @@
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 Settings, { DefaultSetting } from '../../../src/shared/settings/Settings';
import { expect } from 'chai';
class MockSettingRepository implements SettingRepository {
diff --git a/test/settings/components/form/KeymapsForm.test.tsx b/test/settings/components/form/KeymapsForm.test.tsx
index dc2322b..1d1e77c 100644
--- a/test/settings/components/form/KeymapsForm.test.tsx
+++ b/test/settings/components/form/KeymapsForm.test.tsx
@@ -9,7 +9,7 @@ import { expect } from 'chai';
describe("settings/form/KeymapsForm", () => {
describe('render', () => {
it('renders keymap fields', () => {
- let root = ReactTestRenderer.create(<KeymapsForm value={FormKeymaps.valueOf({
+ let root = ReactTestRenderer.create(<KeymapsForm value={FormKeymaps.fromJSON({
'scroll.vertically?{"count":1}': 'j',
'scroll.vertically?{"count":-1}': 'k',
})} />).root
@@ -48,7 +48,7 @@ describe("settings/form/KeymapsForm", () => {
it('invokes onChange event on edit', (done) => {
ReactTestUtils.act(() => {
ReactDOM.render(<KeymapsForm
- value={FormKeymaps.valueOf({
+ value={FormKeymaps.fromJSON({
'scroll.vertically?{"count":1}': 'j',
'scroll.vertically?{"count":-1}': 'k',
})}
diff --git a/test/settings/components/form/PropertiesForm.test.tsx b/test/settings/components/form/PropertiesForm.test.tsx
index 80f60d2..0e33cc8 100644
--- a/test/settings/components/form/PropertiesForm.test.tsx
+++ b/test/settings/components/form/PropertiesForm.test.tsx
@@ -13,14 +13,14 @@ describe("settings/form/PropertiesForm", () => {
mybool: 'boolean',
empty: 'string',
}
- let value = {
+ let values = {
mystr: 'abc',
mynum: 123,
mybool: true,
};
let root = ReactTestRenderer.create(
- <PropertiesForm types={types} value={value} />,
+ <PropertiesForm types={types} value={values} />,
).root
let input = root.findByProps({ name: 'mystr' });
diff --git a/test/settings/components/form/SearchEngineForm.test.tsx b/test/settings/components/form/SearchEngineForm.test.tsx
index 0e6b17d..1f0420d 100644
--- a/test/settings/components/form/SearchEngineForm.test.tsx
+++ b/test/settings/components/form/SearchEngineForm.test.tsx
@@ -8,7 +8,7 @@ import { FormSearch } from 'shared/SettingData';
describe("settings/form/SearchForm", () => {
describe('render', () => {
it('renders SearchForm', () => {
- let root = ReactTestRenderer.create(<SearchForm value={FormSearch.valueOf({
+ let root = ReactTestRenderer.create(<SearchForm value={FormSearch.fromJSON({
default: 'google',
engines: [['google', 'google.com'], ['yahoo', 'yahoo.com']],
})} />).root;
@@ -41,7 +41,7 @@ describe("settings/form/SearchForm", () => {
it('invokes onChange event on edit', (done) => {
ReactTestUtils.act(() => {
ReactDOM.render(<SearchForm
- value={FormSearch.valueOf({
+ value={FormSearch.fromJSON({
default: 'google',
engines: [['google', 'google.com'], ['yahoo', 'yahoo.com']]
})}
@@ -67,7 +67,7 @@ describe("settings/form/SearchForm", () => {
it('invokes onChange event on delete', (done) => {
ReactTestUtils.act(() => {
- ReactDOM.render(<SearchForm value={FormSearch.valueOf({
+ ReactDOM.render(<SearchForm value={FormSearch.fromJSON({
default: 'yahoo',
engines: [['louvre', 'google.com'], ['yahoo', 'yahoo.com']]
})}
@@ -88,7 +88,7 @@ describe("settings/form/SearchForm", () => {
it('invokes onChange event on add', (done) => {
ReactTestUtils.act(() => {
- ReactDOM.render(<SearchForm value={FormSearch.valueOf({
+ ReactDOM.render(<SearchForm value={FormSearch.fromJSON({
default: 'yahoo',
engines: [['google', 'google.com']]
})}
diff --git a/test/shared/SettingData.test.ts b/test/shared/SettingData.test.ts
index 8736ecb..5de7770 100644
--- a/test/shared/SettingData.test.ts
+++ b/test/shared/SettingData.test.ts
@@ -1,8 +1,9 @@
import SettingData, {
- FormKeymaps, JSONSettings, FormSettings,
+ FormKeymaps, JSONTextSettings, FormSettings,
} from '../../src/shared/SettingData';
-import Settings, { Keymaps } from '../../src/shared/Settings';
+import Settings from '../../src/shared/settings/Settings';
import { expect } from 'chai';
+import Keymaps from '../../src/shared/settings/Keymaps';
describe('shared/SettingData', () => {
describe('FormKeymaps', () => {
@@ -11,9 +12,9 @@ describe('shared/SettingData', () => {
let data = {
'scroll.vertically?{"count":1}': 'j',
'scroll.home': '0',
- }
+ };
- let keymaps = FormKeymaps.valueOf(data).toKeymaps();
+ let keymaps = FormKeymaps.fromJSON(data).toKeymaps().toJSON();
expect(keymaps).to.deep.equal({
'j': { type: 'scroll.vertically', count: 1 },
'0': { type: 'scroll.home' },
@@ -23,13 +24,13 @@ describe('shared/SettingData', () => {
describe('#fromKeymaps to #toJSON', () => {
it('create from a Keymaps and create a JSON object', () => {
- let data: Keymaps = {
+ let keymaps: Keymaps = Keymaps.fromJSON({
'j': { type: 'scroll.vertically', count: 1 },
'0': { type: 'scroll.home' },
- }
+ });
- let keymaps = FormKeymaps.fromKeymaps(data).toJSON();
- expect(keymaps).to.deep.equal({
+ let form = FormKeymaps.fromKeymaps(keymaps).toJSON();
+ expect(form).to.deep.equal({
'scroll.vertically?{"count":1}': 'j',
'scroll.home': '0',
});
@@ -56,14 +57,14 @@ describe('shared/SettingData', () => {
"blacklist": []
}`;
- let settings = JSONSettings.valueOf(o).toSettings();
- expect(settings).to.deep.equal(JSON.parse(o));
+ let settings = JSONTextSettings.fromText(o).toSettings();
+ expect(settings.toJSON()).to.deep.equal(JSON.parse(o));
});
});
describe('#fromSettings to #toJSON', () => {
it('create from a Settings and create a JSON string', () => {
- let o = {
+ let o = Settings.fromJSON({
keymaps: {},
search: {
default: "google",
@@ -77,10 +78,10 @@ describe('shared/SettingData', () => {
complete: "sbh"
},
blacklist: [],
- };
+ });
- let json = JSONSettings.fromSettings(o).toJSON();
- expect(JSON.parse(json)).to.deep.equal(o);
+ let json = JSONTextSettings.fromSettings(o).toJSONText();
+ expect(JSON.parse(json)).to.deep.equal(o.toJSON());
});
});
});
@@ -107,8 +108,8 @@ describe('shared/SettingData', () => {
blacklist: []
};
- let settings = FormSettings.valueOf(data).toSettings();
- expect(settings).to.deep.equal({
+ let settings = FormSettings.fromJSON(data).toSettings();
+ expect(settings.toJSON()).to.deep.equal({
keymaps: {
'j': { type: 'scroll.vertically', count: 1 },
'0': { type: 'scroll.home' },
@@ -131,7 +132,7 @@ describe('shared/SettingData', () => {
describe('#fromSettings to #toJSON', () => {
it('create from a Settings and create a JSON string', () => {
- let data: Settings = {
+ let data: Settings = Settings.fromJSON({
keymaps: {
'j': { type: 'scroll.vertically', count: 1 },
'0': { type: 'scroll.home' },
@@ -147,8 +148,8 @@ describe('shared/SettingData', () => {
smoothscroll: false,
complete: "sbh"
},
- blacklist: []
- };
+ blacklist: [],
+ });
let json = FormSettings.fromSettings(data).toJSON();
expect(json).to.deep.equal({
@@ -195,7 +196,7 @@ describe('shared/SettingData', () => {
}`,
};
- let j = SettingData.valueOf(data).toJSON();
+ let j = SettingData.fromJSON(data).toJSON();
expect(j.source).to.equal('json');
expect(j.json).to.be.a('string');
});
@@ -220,7 +221,7 @@ describe('shared/SettingData', () => {
},
};
- let j = SettingData.valueOf(data).toJSON();
+ let j = SettingData.fromJSON(data).toJSON();
expect(j.source).to.equal('form');
expect(j.form).to.deep.equal({
keymaps: {},
@@ -261,8 +262,8 @@ describe('shared/SettingData', () => {
}`,
};
- let settings = SettingData.valueOf(data).toSettings();
- expect(settings.search.default).to.equal('google');
+ let settings = SettingData.fromJSON(data).toSettings();
+ expect(settings.search.defaultEngine).to.equal('google');
});
it('parse object from form source', () => {
@@ -285,8 +286,8 @@ describe('shared/SettingData', () => {
},
};
- let settings = SettingData.valueOf(data).toSettings();
- expect(settings.search.default).to.equal('yahoo');
+ let settings = SettingData.fromJSON(data).toSettings();
+ expect(settings.search.defaultEngine).to.equal('yahoo');
});
});
});
diff --git a/test/shared/Settings.test.ts b/test/shared/Settings.test.ts
deleted file mode 100644
index 04b28c4..0000000
--- a/test/shared/Settings.test.ts
+++ /dev/null
@@ -1,194 +0,0 @@
-import * as settings from '../../src/shared/Settings';
-import { expect } from 'chai';
-
-describe('Settings', () => {
- describe('#keymapsValueOf', () => {
- it('returns empty object by empty settings', () => {
- let keymaps = settings.keymapsValueOf({});
- expect(keymaps).to.be.empty;
- });
-
- it('returns keymaps by valid settings', () => {
- let keymaps = settings.keymapsValueOf({
- k: { type: "scroll.vertically", count: -1 },
- j: { type: "scroll.vertically", count: 1 },
- });
-
- expect(keymaps['k']).to.deep.equal({ type: "scroll.vertically", count: -1 });
- expect(keymaps['j']).to.deep.equal({ type: "scroll.vertically", count: 1 });
- });
-
- it('throws a TypeError by invalid settings', () => {
- expect(() => settings.keymapsValueOf(null)).to.throw(TypeError);
- expect(() => settings.keymapsValueOf({
- k: { type: "invalid.operation" },
- })).to.throw(TypeError);
- });
- });
-
- describe('#searchValueOf', () => {
- it('returns search settings by valid settings', () => {
- let search = settings.searchValueOf({
- default: "google",
- engines: {
- "google": "https://google.com/search?q={}",
- "yahoo": "https://search.yahoo.com/search?p={}",
- }
- });
-
- expect(search).to.deep.equal({
- default: "google",
- engines: {
- "google": "https://google.com/search?q={}",
- "yahoo": "https://search.yahoo.com/search?p={}",
- }
- });
- });
-
- it('throws a TypeError by invalid settings', () => {
- expect(() => settings.searchValueOf(null)).to.throw(TypeError);
- expect(() => settings.searchValueOf({})).to.throw(TypeError);
- expect(() => settings.searchValueOf([])).to.throw(TypeError);
- expect(() => settings.searchValueOf({
- default: 123,
- engines: {}
- })).to.throw(TypeError);
- expect(() => settings.searchValueOf({
- default: "google",
- engines: {
- "google": 123456,
- }
- })).to.throw(TypeError);
- expect(() => settings.searchValueOf({
- default: "wikipedia",
- engines: {
- "google": "https://google.com/search?q={}",
- "yahoo": "https://search.yahoo.com/search?p={}",
- }
- })).to.throw(TypeError);
- expect(() => settings.searchValueOf({
- default: "g o o g l e",
- engines: {
- "g o o g l e": "https://google.com/search?q={}",
- }
- })).to.throw(TypeError);
- expect(() => settings.searchValueOf({
- default: "google",
- engines: {
- "google": "https://google.com/search",
- }
- })).to.throw(TypeError);
- expect(() => settings.searchValueOf({
- default: "google",
- engines: {
- "google": "https://google.com/search?q={}&r={}",
- }
- })).to.throw(TypeError);
- });
- });
-
- describe('#propertiesValueOf', () => {
- it('returns with default properties by empty settings', () => {
- let props = settings.propertiesValueOf({});
- expect(props).to.deep.equal({
- hintchars: "abcdefghijklmnopqrstuvwxyz",
- smoothscroll: false,
- complete: "sbh"
- })
- });
-
- it('returns properties by valid settings', () => {
- let props = settings.propertiesValueOf({
- hintchars: "abcdefgh",
- smoothscroll: false,
- complete: "sbh"
- });
-
- expect(props).to.deep.equal({
- hintchars: "abcdefgh",
- smoothscroll: false,
- complete: "sbh"
- });
- });
-
- it('throws a TypeError by invalid settings', () => {
- expect(() => settings.keymapsValueOf(null)).to.throw(TypeError);
- expect(() => settings.keymapsValueOf({
- smoothscroll: 'false',
- })).to.throw(TypeError);
- expect(() => settings.keymapsValueOf({
- unknown: 'xyz'
- })).to.throw(TypeError);
- });
- });
-
- describe('#blacklistValueOf', () => {
- it('returns empty array by empty settings', () => {
- let blacklist = settings.blacklistValueOf([]);
- expect(blacklist).to.be.empty;
- });
-
- it('returns blacklist by valid settings', () => {
- let blacklist = settings.blacklistValueOf([
- "github.com",
- "circleci.com",
- ]);
-
- expect(blacklist).to.deep.equal([
- "github.com",
- "circleci.com",
- ]);
- });
-
- it('throws a TypeError by invalid settings', () => {
- expect(() => settings.blacklistValueOf(null)).to.throw(TypeError);
- expect(() => settings.blacklistValueOf({})).to.throw(TypeError);
- expect(() => settings.blacklistValueOf([1,2,3])).to.throw(TypeError);
- });
- });
-
- describe('#valueOf', () => {
- it('returns settings by valid settings', () => {
- let x = settings.valueOf({
- keymaps: {},
- "search": {
- "default": "google",
- "engines": {
- "google": "https://google.com/search?q={}",
- }
- },
- "properties": {},
- "blacklist": []
- });
-
- expect(x).to.deep.equal({
- keymaps: {},
- search: {
- default: "google",
- engines: {
- google: "https://google.com/search?q={}",
- }
- },
- properties: {
- hintchars: "abcdefghijklmnopqrstuvwxyz",
- smoothscroll: false,
- complete: "sbh"
- },
- blacklist: []
- });
- });
-
- it('sets default settings', () => {
- let value = settings.valueOf({});
- expect(value.keymaps).to.not.be.empty;
- expect(value.properties).to.not.be.empty;
- expect(value.search.default).to.be.a('string');
- expect(value.search.engines).to.be.an('object');
- expect(value.blacklist).to.be.empty;
- });
-
- it('throws a TypeError with an unknown field', () => {
- expect(() => settings.valueOf({ name: 'alice' })).to.throw(TypeError)
- });
- });
-});
diff --git a/test/shared/blacklists.test.ts b/test/shared/blacklists.test.ts
deleted file mode 100644
index 289ea0f..0000000
--- a/test/shared/blacklists.test.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-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;
- })
-
- it('matches address and port', () => {
- let blacklist = ['127.0.0.1:8888'];
-
- expect(includes(blacklist, 'http://127.0.0.1:8888/')).to.be.true;
- expect(includes(blacklist, 'http://127.0.0.1:8888/hello')).to.be.true;
- })
-});
diff --git a/test/shared/properties.test.js b/test/shared/properties.test.js
deleted file mode 100644
index 37903d8..0000000
--- a/test/shared/properties.test.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import * as settings from 'shared/settings';
-
-describe('properties', () => {
- describe('Def class', () => {
- it('returns property definitions', () => {
- let def = new proerties.Def(
- 'smoothscroll',
- 'smooth scroll',
- false);
-
- expect(def.name).to.equal('smoothscroll');
- expect(def.describe).to.equal('smooth scroll');
- expect(def.defaultValue).to.equal(false);
- expect(def.type).to.equal('boolean');
- });
- });
-});
-
diff --git a/test/shared/property-defs.test.js b/test/shared/property-defs.test.js
deleted file mode 100644
index 37903d8..0000000
--- a/test/shared/property-defs.test.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import * as settings from 'shared/settings';
-
-describe('properties', () => {
- describe('Def class', () => {
- it('returns property definitions', () => {
- let def = new proerties.Def(
- 'smoothscroll',
- 'smooth scroll',
- false);
-
- expect(def.name).to.equal('smoothscroll');
- expect(def.describe).to.equal('smooth scroll');
- expect(def.defaultValue).to.equal(false);
- expect(def.type).to.equal('boolean');
- });
- });
-});
-
diff --git a/test/shared/settings/Blacklist.test.ts b/test/shared/settings/Blacklist.test.ts
new file mode 100644
index 0000000..fbacf5d
--- /dev/null
+++ b/test/shared/settings/Blacklist.test.ts
@@ -0,0 +1,77 @@
+import Blacklist from '../../../src/shared/settings/Blacklist';
+import { expect } from 'chai';
+
+describe('Blacklist', () => {
+ describe('fromJSON', () => {
+ it('returns empty array by empty settings', () => {
+ let blacklist = Blacklist.fromJSON([]);
+ expect(blacklist.toJSON()).to.be.empty;
+ });
+
+ it('returns blacklist by valid settings', () => {
+ let blacklist = Blacklist.fromJSON([
+ 'github.com',
+ 'circleci.com',
+ ]);
+
+ expect(blacklist.toJSON()).to.deep.equal([
+ 'github.com',
+ 'circleci.com',
+ ]);
+ });
+
+ it('throws a TypeError by invalid settings', () => {
+ expect(() => Blacklist.fromJSON(null)).to.throw(TypeError);
+ expect(() => Blacklist.fromJSON({})).to.throw(TypeError);
+ expect(() => Blacklist.fromJSON([1,2,3])).to.throw(TypeError);
+ });
+ });
+
+ describe('#includes', () => {
+ it('matches by *', () => {
+ let blacklist = new Blacklist(['*']);
+
+ expect(blacklist.includes('https://github.com/abc')).to.be.true;
+ });
+
+ it('matches by hostname', () => {
+ let blacklist = new Blacklist(['github.com']);
+
+ expect(blacklist.includes('https://github.com')).to.be.true;
+ expect(blacklist.includes('https://gist.github.com')).to.be.false;
+ expect(blacklist.includes('https://github.com/ueokande')).to.be.true;
+ expect(blacklist.includes('https://github.org')).to.be.false;
+ expect(blacklist.includes('https://google.com/search?q=github.org')).to.be.false;
+ });
+
+ it('matches by hostname with wildcard', () => {
+ let blacklist = new Blacklist(['*.github.com']);
+
+ expect(blacklist.includes('https://github.com')).to.be.false;
+ expect(blacklist.includes('https://gist.github.com')).to.be.true;
+ })
+
+ it('matches by path', () => {
+ let blacklist = new Blacklist(['github.com/abc']);
+
+ expect(blacklist.includes('https://github.com/abc')).to.be.true;
+ expect(blacklist.includes('https://github.com/abcdef')).to.be.false;
+ expect(blacklist.includes('https://gist.github.com/abc')).to.be.false;
+ })
+
+ it('matches by path with wildcard', () => {
+ let blacklist = new Blacklist(['github.com/abc*']);
+
+ expect(blacklist.includes('https://github.com/abc')).to.be.true;
+ expect(blacklist.includes('https://github.com/abcdef')).to.be.true;
+ expect(blacklist.includes('https://gist.github.com/abc')).to.be.false;
+ })
+
+ it('matches address and port', () => {
+ let blacklist = new Blacklist(['127.0.0.1:8888']);
+
+ expect(blacklist.includes('http://127.0.0.1:8888/')).to.be.true;
+ expect(blacklist.includes('http://127.0.0.1:8888/hello')).to.be.true;
+ })
+ })
+});
diff --git a/test/shared/settings/Key.test.ts b/test/shared/settings/Key.test.ts
new file mode 100644
index 0000000..8222d5a
--- /dev/null
+++ b/test/shared/settings/Key.test.ts
@@ -0,0 +1,92 @@
+import { expect } from 'chai'
+import Key from '../../../src/shared/settings/Key';
+
+describe("Key", () => {
+ describe('fromMapKey', () => {
+ it('return for X', () => {
+ let key = Key.fromMapKey('x');
+ expect(key.key).to.equal('x');
+ expect(key.shift).to.be.false;
+ expect(key.ctrl).to.be.false;
+ expect(key.alt).to.be.false;
+ expect(key.meta).to.be.false;
+ });
+
+ it('return for Shift+X', () => {
+ let key = Key.fromMapKey('X');
+ expect(key.key).to.equal('X');
+ expect(key.shift).to.be.true;
+ expect(key.ctrl).to.be.false;
+ expect(key.alt).to.be.false;
+ expect(key.meta).to.be.false;
+ });
+
+ it('return for Ctrl+X', () => {
+ let key = Key.fromMapKey('<C-X>');
+ expect(key.key).to.equal('x');
+ expect(key.shift).to.be.false;
+ expect(key.ctrl).to.be.true;
+ expect(key.alt).to.be.false;
+ expect(key.meta).to.be.false;
+ });
+
+ it('returns for Ctrl+Meta+X', () => {
+ let key = Key.fromMapKey('<C-M-X>');
+ expect(key.key).to.equal('x');
+ expect(key.shift).to.be.false;
+ expect(key.ctrl).to.be.true;
+ expect(key.alt).to.be.false;
+ expect(key.meta).to.be.true;
+ });
+
+ it('returns for Ctrl+Shift+x', () => {
+ let key = Key.fromMapKey('<C-S-x>');
+ expect(key.key).to.equal('X');
+ expect(key.shift).to.be.true;
+ expect(key.ctrl).to.be.true;
+ expect(key.alt).to.be.false;
+ expect(key.meta).to.be.false;
+ });
+
+ it('returns for Shift+Esc', () => {
+ let key = Key.fromMapKey('<S-Esc>');
+ expect(key.key).to.equal('Esc');
+ expect(key.shift).to.be.true;
+ expect(key.ctrl).to.be.false;
+ expect(key.alt).to.be.false;
+ expect(key.meta).to.be.false;
+ });
+
+ it('returns for Ctrl+Esc', () => {
+ let key = Key.fromMapKey('<C-Esc>');
+ expect(key.key).to.equal('Esc');
+ expect(key.shift).to.be.false;
+ expect(key.ctrl).to.be.true;
+ expect(key.alt).to.be.false;
+ expect(key.meta).to.be.false;
+ });
+
+ it('returns for Ctrl+Esc', () => {
+ let key = Key.fromMapKey('<C-Space>');
+ expect(key.key).to.equal('Space');
+ expect(key.shift).to.be.false;
+ expect(key.ctrl).to.be.true;
+ expect(key.alt).to.be.false;
+ expect(key.meta).to.be.false;
+ });
+ });
+
+ describe('equals', () => {
+ expect(new Key({
+ key: 'x', shift: false, ctrl: true, alt: false, meta: false,
+ }).equals(new Key({
+ key: 'x', shift: false, ctrl: true, alt: false, meta: false,
+ }))).to.be.true;
+
+ expect(new Key({
+ key: 'x', shift: false, ctrl: false, alt: false, meta: false,
+ }).equals(new Key({
+ key: 'X', shift: true, ctrl: false, alt: false, meta: false,
+ }))).to.be.false;
+ });
+});
diff --git a/test/shared/settings/KeySequence.test.ts b/test/shared/settings/KeySequence.test.ts
new file mode 100644
index 0000000..361cbd1
--- /dev/null
+++ b/test/shared/settings/KeySequence.test.ts
@@ -0,0 +1,72 @@
+import KeySequence from '../../../src/shared/settings/KeySequence';
+import { expect } from 'chai'
+import Key from "../../../src/shared/settings/Key";
+
+describe("KeySequence", () => {
+ describe('#push', () => {
+ it('append a key to the sequence', () => {
+ let seq = new KeySequence([]);
+ seq.push(Key.fromMapKey('g'));
+ seq.push(Key.fromMapKey('<S-U>'));
+
+ expect(seq.keys[0].key).to.equal('g');
+ expect(seq.keys[1].key).to.equal('U');
+ expect(seq.keys[1].shift).to.be.true;
+ })
+ });
+
+ describe('#startsWith', () => {
+ it('returns true if the key sequence starts with param', () => {
+ let seq = new KeySequence([
+ Key.fromMapKey('g'),
+ Key.fromMapKey('<S-U>'),
+ ]);
+
+ expect(seq.startsWith(new KeySequence([
+ ]))).to.be.true;
+ expect(seq.startsWith(new KeySequence([
+ Key.fromMapKey('g'),
+ ]))).to.be.true;
+ expect(seq.startsWith(new KeySequence([
+ Key.fromMapKey('g'), Key.fromMapKey('<S-U>'),
+ ]))).to.be.true;
+ expect(seq.startsWith(new KeySequence([
+ Key.fromMapKey('g'), Key.fromMapKey('<S-U>'), Key.fromMapKey('x'),
+ ]))).to.be.false;
+ expect(seq.startsWith(new KeySequence([
+ Key.fromMapKey('h'),
+ ]))).to.be.false;
+ });
+
+ it('returns true if the empty sequence starts with an empty sequence', () => {
+ let seq = new KeySequence([]);
+
+ expect(seq.startsWith(new KeySequence([]))).to.be.true;
+ expect(seq.startsWith(new KeySequence([
+ Key.fromMapKey('h'),
+ ]))).to.be.false;
+ })
+ });
+
+ describe('#fromMapKeys', () => {
+ it('returns mapped keys for Shift+Esc', () => {
+ let keys = KeySequence.fromMapKeys('<S-Esc>').keys;
+ expect(keys).to.have.lengthOf(1);
+ expect(keys[0].key).to.equal('Esc');
+ expect(keys[0].shift).to.be.true;
+ });
+
+ it('returns mapped keys for a<C-B><A-C>d<M-e>', () => {
+ let keys = KeySequence.fromMapKeys('a<C-B><A-C>d<M-e>').keys;
+ expect(keys).to.have.lengthOf(5);
+ expect(keys[0].key).to.equal('a');
+ expect(keys[1].ctrl).to.be.true;
+ expect(keys[1].key).to.equal('b');
+ expect(keys[2].alt).to.be.true;
+ expect(keys[2].key).to.equal('c');
+ expect(keys[3].key).to.equal('d');
+ expect(keys[4].meta).to.be.true;
+ expect(keys[4].key).to.equal('e');
+ });
+ })
+});
diff --git a/test/shared/settings/Keymaps.test.ts b/test/shared/settings/Keymaps.test.ts
new file mode 100644
index 0000000..7896a63
--- /dev/null
+++ b/test/shared/settings/Keymaps.test.ts
@@ -0,0 +1,66 @@
+import Keymaps from '../../../src/shared/settings/Keymaps';
+import { expect } from 'chai';
+
+describe('Keymaps', () => {
+ describe('#valueOf', () => {
+ it('returns empty object by empty settings', () => {
+ let keymaps = Keymaps.fromJSON({}).toJSON();
+ expect(keymaps).to.be.empty;
+ });
+
+ it('returns keymaps by valid settings', () => {
+ let keymaps = Keymaps.fromJSON({
+ k: { type: "scroll.vertically", count: -1 },
+ j: { type: "scroll.vertically", count: 1 },
+ }).toJSON();
+
+ expect(keymaps['k']).to.deep.equal({ type: "scroll.vertically", count: -1 });
+ expect(keymaps['j']).to.deep.equal({ type: "scroll.vertically", count: 1 });
+ });
+
+ it('throws a TypeError by invalid settings', () => {
+ expect(() => Keymaps.fromJSON(null)).to.throw(TypeError);
+ expect(() => Keymaps.fromJSON({
+ k: { type: "invalid.operation" },
+ })).to.throw(TypeError);
+ });
+ });
+
+ describe('#combine', () => {
+ it('returns combined keymaps', () => {
+ let keymaps = Keymaps.fromJSON({
+ k: { type: "scroll.vertically", count: -1 },
+ j: { type: "scroll.vertically", count: 1 },
+ }).combine(Keymaps.fromJSON({
+ n: { type: "find.next" },
+ N: { type: "find.prev" },
+ }));
+
+ let entries = keymaps.entries().sort(([name1], [name2]) => name1.localeCompare(name2));
+ expect(entries).deep.equals([
+ ['j', { type: "scroll.vertically", count: 1 }],
+ ['k', { type: "scroll.vertically", count: -1 }],
+ ['n', { type: "find.next" }],
+ ['N', { type: "find.prev" }],
+ ]);
+ });
+
+ it('overrides current keymaps', () => {
+ let keymaps = Keymaps.fromJSON({
+ k: { type: "scroll.vertically", count: -1 },
+ j: { type: "scroll.vertically", count: 1 },
+ }).combine(Keymaps.fromJSON({
+ n: { type: "find.next" },
+ j: { type: "find.prev" },
+ }));
+
+ let entries = keymaps.entries().sort(([name1], [name2]) => name1.localeCompare(name2));
+ expect(entries).deep.equals([
+ ['j', { type: "find.prev" }],
+ ['k', { type: "scroll.vertically", count: -1 }],
+ ['n', { type: "find.next" }],
+ ]);
+ });
+ });
+});
+
diff --git a/test/shared/settings/Properties.test.ts b/test/shared/settings/Properties.test.ts
new file mode 100644
index 0000000..609a565
--- /dev/null
+++ b/test/shared/settings/Properties.test.ts
@@ -0,0 +1,30 @@
+import Properties from '../../../src/shared/settings/Properties';
+import { expect } from 'chai';
+
+describe('Properties', () => {
+ describe('#propertiesValueOf', () => {
+ it('returns with default properties by empty settings', () => {
+ let props = Properties.fromJSON({});
+ expect(props).to.deep.equal({
+ hintchars: "abcdefghijklmnopqrstuvwxyz",
+ smoothscroll: false,
+ complete: "sbh"
+ })
+ });
+
+ it('returns properties by valid settings', () => {
+ let props = Properties.fromJSON({
+ hintchars: "abcdefgh",
+ smoothscroll: false,
+ complete: "sbh"
+ });
+
+ expect(props).to.deep.equal({
+ hintchars: "abcdefgh",
+ smoothscroll: false,
+ complete: "sbh"
+ });
+ });
+ });
+});
+
diff --git a/test/shared/settings/Search.test.ts b/test/shared/settings/Search.test.ts
new file mode 100644
index 0000000..7c9134d
--- /dev/null
+++ b/test/shared/settings/Search.test.ts
@@ -0,0 +1,68 @@
+import Search from '../../../src/shared/settings/Search';
+import { expect } from 'chai';
+
+describe('Search', () => {
+ it('returns search settings by valid settings', () => {
+ let search = Search.fromJSON({
+ default: 'google',
+ engines: {
+ 'google': 'https://google.com/search?q={}',
+ 'yahoo': 'https://search.yahoo.com/search?p={}',
+ }
+ });
+
+ expect(search.defaultEngine).to.equal('google')
+ expect(search.engines).to.deep.equals({
+ 'google': 'https://google.com/search?q={}',
+ 'yahoo': 'https://search.yahoo.com/search?p={}',
+ });
+ expect(search.toJSON()).to.deep.equal({
+ default: 'google',
+ engines: {
+ 'google': 'https://google.com/search?q={}',
+ 'yahoo': 'https://search.yahoo.com/search?p={}',
+ }
+ });
+ });
+
+ it('throws a TypeError by invalid settings', () => {
+ expect(() => Search.fromJSON(null)).to.throw(TypeError);
+ expect(() => Search.fromJSON({})).to.throw(TypeError);
+ expect(() => Search.fromJSON([])).to.throw(TypeError);
+ expect(() => Search.fromJSON({
+ default: 123,
+ engines: {}
+ })).to.throw(TypeError);
+ expect(() => Search.fromJSON({
+ default: 'google',
+ engines: {
+ 'google': 123456,
+ }
+ })).to.throw(TypeError);
+ expect(() => Search.fromJSON({
+ default: 'wikipedia',
+ engines: {
+ 'google': 'https://google.com/search?q={}',
+ 'yahoo': 'https://search.yahoo.com/search?p={}',
+ }
+ })).to.throw(TypeError);
+ expect(() => Search.fromJSON({
+ default: 'g o o g l e',
+ engines: {
+ 'g o o g l e': 'https://google.com/search?q={}',
+ }
+ })).to.throw(TypeError);
+ expect(() => Search.fromJSON({
+ default: 'google',
+ engines: {
+ 'google': 'https://google.com/search',
+ }
+ })).to.throw(TypeError);
+ expect(() => Search.fromJSON({
+ default: 'google',
+ engines: {
+ 'google': 'https://google.com/search?q={}&r={}',
+ }
+ })).to.throw(TypeError);
+ });
+});
diff --git a/test/shared/settings/Settings.test.ts b/test/shared/settings/Settings.test.ts
new file mode 100644
index 0000000..ab6af04
--- /dev/null
+++ b/test/shared/settings/Settings.test.ts
@@ -0,0 +1,54 @@
+import Settings from '../../../src/shared/settings/Settings';
+import { expect } from 'chai';
+
+describe('Settings', () => {
+ describe('#valueOf', () => {
+ it('returns settings by valid settings', () => {
+ let x = Settings.fromJSON({
+ keymaps: {},
+ "search": {
+ "default": "google",
+ "engines": {
+ "google": "https://google.com/search?q={}",
+ }
+ },
+ "properties": {},
+ "blacklist": []
+ });
+
+ expect({
+ keymaps: x.keymaps.toJSON(),
+ search: x.search.toJSON(),
+ properties: x.properties.toJSON(),
+ blacklist: x.blacklist.toJSON(),
+ }).to.deep.equal({
+ keymaps: {},
+ search: {
+ default: "google",
+ engines: {
+ google: "https://google.com/search?q={}",
+ }
+ },
+ properties: {
+ hintchars: "abcdefghijklmnopqrstuvwxyz",
+ smoothscroll: false,
+ complete: "sbh"
+ },
+ blacklist: []
+ });
+ });
+
+ it('sets default settings', () => {
+ let value = Settings.fromJSON({});
+ expect(value.keymaps.toJSON()).to.not.be.empty;
+ expect(value.properties.toJSON()).to.not.be.empty;
+ expect(value.search.defaultEngine).to.be.a('string');
+ expect(value.search.engines).to.be.an('object');
+ expect(value.blacklist.toJSON()).to.be.empty;
+ });
+
+ it('throws a TypeError with an unknown field', () => {
+ expect(() => Settings.fromJSON({ name: 'alice' })).to.throw(TypeError)
+ });
+ });
+});
diff --git a/test/shared/urls.test.ts b/test/shared/urls.test.ts
index f2950b6..3a3eea6 100644
--- a/test/shared/urls.test.ts
+++ b/test/shared/urls.test.ts
@@ -1,14 +1,16 @@
-import * as parsers from 'shared/urls';
+import * as parsers from '../../src/shared/urls';
+import { expect } from 'chai';
+import Search from '../../src/shared/settings/Search';
describe("shared/commands/parsers", () => {
describe('#searchUrl', () => {
- const config = {
+ const config = Search.fromJSON({
default: 'google',
engines: {
google: 'https://google.com/search?q={}',
yahoo: 'https://yahoo.com/search?q={}',
}
- };
+ });
it('convertes search url', () => {
expect(parsers.searchUrl('google.com', config))
diff --git a/test/shared/utils/re.test.ts b/test/shared/utils/re.test.ts
deleted file mode 100644
index d12ceb7..0000000
--- a/test/shared/utils/re.test.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import * as re from 'shared/utils/re';
-
-describe("re util", () => {
- it('matches by pattern', () => {
- let regex = re.fromWildcard('*.example.com/*');
- expect('foo.example.com/bar').to.match(regex);
- expect('foo.example.com').not.to.match(regex);
- expect('example.com/bar').not.to.match(regex);
-
- regex = re.fromWildcard('example.com/*')
- expect('example.com/foo').to.match(regex);
- expect('example.com/').to.match(regex);
-
- regex = re.fromWildcard('example.com/*bar')
- expect('example.com/foobar').to.match(regex);
- expect('example.com/bar').to.match(regex);
- expect('example.com/foobarfoo').not.to.match(regex);
- })
-});