aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShin'ya Ueoka <ueokande@i-beam.org>2020-02-09 11:13:55 +0900
committerShin'ya Ueoka <ueokande@i-beam.org>2020-02-09 11:29:13 +0900
commitb2fc46ebf79ebb1ffa068fb513d1eeb9b50d7b3f (patch)
tree7f3037b2f4f1f8ca8082e2fd573c5bab03a490aa
parent4d5573356e30721431b74351d93691d6ce4da4a8 (diff)
Add SettingUseCase tests
-rw-r--r--src/background/Application.ts6
-rw-r--r--src/background/di.ts11
-rw-r--r--src/background/index.ts1
-rw-r--r--src/background/presenters/Notifier.ts (renamed from src/background/presenters/NotifyPresenter.ts)11
-rw-r--r--src/background/repositories/CachedSettingRepository.ts21
-rw-r--r--src/background/repositories/LocalSettingRepository.ts24
-rw-r--r--src/background/repositories/SettingRepository.ts49
-rw-r--r--src/background/repositories/SyncSettingRepository.ts25
-rw-r--r--src/background/usecases/CommandUseCase.ts8
-rw-r--r--src/background/usecases/CompletionsUseCase.ts8
-rw-r--r--src/background/usecases/SettingUseCase.ts21
-rw-r--r--src/background/usecases/VersionUseCase.ts8
-rw-r--r--test/background/usecases/SettingUseCase.test.ts161
13 files changed, 269 insertions, 85 deletions
diff --git a/src/background/Application.ts b/src/background/Application.ts
index da6bdfc..c2c48b5 100644
--- a/src/background/Application.ts
+++ b/src/background/Application.ts
@@ -1,8 +1,8 @@
-import { injectable } from 'tsyringe';
+import { injectable, inject } from 'tsyringe';
import ContentMessageListener from './infrastructures/ContentMessageListener';
import SettingController from './controllers/SettingController';
import VersionController from './controllers/VersionController';
-import SyncSettingRepository from "./repositories/SyncSettingRepository";
+import SettingRepository from "./repositories/SettingRepository";
@injectable()
export default class Application {
@@ -10,7 +10,7 @@ export default class Application {
private contentMessageListener: ContentMessageListener,
private settingController: SettingController,
private versionController: VersionController,
- private syncSettingRepository: SyncSettingRepository
+ @inject("SyncSettingRepository") private syncSettingRepository: SettingRepository,
) {
}
diff --git a/src/background/di.ts b/src/background/di.ts
new file mode 100644
index 0000000..9fc230c
--- /dev/null
+++ b/src/background/di.ts
@@ -0,0 +1,11 @@
+/* eslint-disable max-len */
+
+import { LocalSettingRepository, SyncSettingRepository } from "./repositories/SettingRepository";
+import { NotifierImpl } from "./presenters/Notifier";
+import { CachedSettingRepositoryImpl } from "./repositories/CachedSettingRepository";
+import { container } from 'tsyringe';
+
+container.register('LocalSettingRepository', { useValue: LocalSettingRepository });
+container.register('SyncSettingRepository', { useClass: SyncSettingRepository });
+container.register('CachedSettingRepository', { useClass: CachedSettingRepositoryImpl });
+container.register('Notifier', { useClass: NotifierImpl });
diff --git a/src/background/index.ts b/src/background/index.ts
index 51fde56..f75c53b 100644
--- a/src/background/index.ts
+++ b/src/background/index.ts
@@ -1,6 +1,7 @@
import 'reflect-metadata';
import { container } from 'tsyringe';
import Application from './Application';
+import './di';
const app = container.resolve(Application);
app.run();
diff --git a/src/background/presenters/NotifyPresenter.ts b/src/background/presenters/Notifier.ts
index b7f4f99..57d58cb 100644
--- a/src/background/presenters/NotifyPresenter.ts
+++ b/src/background/presenters/Notifier.ts
@@ -1,10 +1,13 @@
-import { injectable } from 'tsyringe';
-
const NOTIFICATION_ID_UPDATE = 'vimvixen-update';
const NOTIFICATION_ID_INVALID_SETTINGS = 'vimvixen-update-invalid-settings';
-@injectable()
-export default class NotifyPresenter {
+export default interface Notifier {
+ notifyUpdated(version: string, onclick: () => void): Promise<void>;
+
+ notifyInvalidSettings(onclick: () => void): Promise<void>;
+}
+
+export class NotifierImpl implements NotifierImpl {
async notifyUpdated(version: string, onclick: () => void): Promise<void> {
const title = `Vim Vixen ${version} has been installed`;
const message = 'Click here to see release notes';
diff --git a/src/background/repositories/CachedSettingRepository.ts b/src/background/repositories/CachedSettingRepository.ts
index b73d94a..1af15d4 100644
--- a/src/background/repositories/CachedSettingRepository.ts
+++ b/src/background/repositories/CachedSettingRepository.ts
@@ -1,12 +1,20 @@
-import { injectable } from 'tsyringe';
import MemoryStorage from '../infrastructures/MemoryStorage';
import Settings from '../../shared/settings/Settings';
import Properties from '../../shared/settings/Properties';
const CACHED_SETTING_KEY = 'setting';
-@injectable()
-export default class CachedSettingRepository {
+export default interface CachedSettingRepository {
+ get(): Promise<Settings>;
+
+ update(value: Settings): Promise<void>;
+
+ setProperty(
+ name: string, value: string | number | boolean,
+ ): Promise<void>;
+}
+
+export class CachedSettingRepositoryImpl implements CachedSettingRepository {
private cache: MemoryStorage;
constructor() {
@@ -18,8 +26,9 @@ export default class CachedSettingRepository {
return Promise.resolve(Settings.fromJSON(data));
}
- update(value: Settings): void {
- return this.cache.set(CACHED_SETTING_KEY, value.toJSON());
+ update(value: Settings): Promise<void> {
+ this.cache.set(CACHED_SETTING_KEY, value.toJSON());
+ return Promise.resolve()
}
async setProperty(
@@ -49,6 +58,6 @@ export default class CachedSettingRepository {
current.properties.complete = newValue as string;
break;
}
- return this.update(current);
+ await this.update(current);
}
}
diff --git a/src/background/repositories/LocalSettingRepository.ts b/src/background/repositories/LocalSettingRepository.ts
deleted file mode 100644
index 0c9ef3b..0000000
--- a/src/background/repositories/LocalSettingRepository.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { injectable } from 'tsyringe';
-import SettingData from '../../shared/SettingData';
-
-@injectable()
-export default class LocalSettingRepository {
- async load(): Promise<SettingData | null> {
- const {settings} = await browser.storage.local.get('settings');
- if (!settings) {
- return null;
- }
- return SettingData.fromJSON(settings as any);
- }
-
- onChange(callback: () => void) {
- browser.storage.onChanged.addListener((changes, area) => {
- if (area !== 'local') {
- return;
- }
- if (changes.settings) {
- callback();
- }
- });
- }
-}
diff --git a/src/background/repositories/SettingRepository.ts b/src/background/repositories/SettingRepository.ts
new file mode 100644
index 0000000..b522045
--- /dev/null
+++ b/src/background/repositories/SettingRepository.ts
@@ -0,0 +1,49 @@
+import SettingData from '../../shared/SettingData';
+
+export default interface SettingRepository {
+ load(): Promise<SettingData | null>;
+
+ onChange(callback: () => void): void;
+}
+
+export class LocalSettingRepository implements SettingRepository {
+ async load(): Promise<SettingData | null> {
+ const {settings} = await browser.storage.local.get('settings');
+ if (!settings) {
+ return null;
+ }
+ return SettingData.fromJSON(settings as any);
+ }
+
+ onChange(callback: () => void) {
+ browser.storage.onChanged.addListener((changes, area) => {
+ if (area !== 'local') {
+ return;
+ }
+ if (changes.settings) {
+ callback();
+ }
+ });
+ }
+}
+
+export class SyncSettingRepository implements SettingRepository {
+ async load(): Promise<SettingData | null> {
+ const {settings} = await browser.storage.sync.get('settings');
+ if (!settings) {
+ return null;
+ }
+ return SettingData.fromJSON(settings as any);
+ }
+
+ onChange(callback: () => void) {
+ browser.storage.onChanged.addListener((changes, area) => {
+ if (area !== 'sync') {
+ return;
+ }
+ if (changes.settings) {
+ callback();
+ }
+ });
+ }
+} \ No newline at end of file
diff --git a/src/background/repositories/SyncSettingRepository.ts b/src/background/repositories/SyncSettingRepository.ts
deleted file mode 100644
index 9f59e61..0000000
--- a/src/background/repositories/SyncSettingRepository.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { injectable } from 'tsyringe';
-import SettingData from '../../shared/SettingData';
-
-@injectable()
-export default class SyncSettingRepository {
- async load(): Promise<SettingData | null> {
- const { settings } = await browser.storage.sync.get('settings');
- if (!settings) {
- return null;
- }
- return SettingData.fromJSON(settings as any);
- }
-
- onChange(callback: () => void) {
- browser.storage.onChanged.addListener((changes, area) => {
- if (area !== 'sync') {
- return;
- }
- if (changes.settings) {
- callback();
- }
- });
- }
-}
-
diff --git a/src/background/usecases/CommandUseCase.ts b/src/background/usecases/CommandUseCase.ts
index 20ded7e..7dba664 100644
--- a/src/background/usecases/CommandUseCase.ts
+++ b/src/background/usecases/CommandUseCase.ts
@@ -1,4 +1,4 @@
-import { injectable } from 'tsyringe';
+import { injectable, inject } from 'tsyringe';
import * as operations from '../../shared/operations';
import * as parsers from './parsers';
import * as urls from '../../shared/urls';
@@ -17,7 +17,7 @@ export default class CommandIndicator {
private tabPresenter: TabPresenter,
private windowPresenter: WindowPresenter,
private helpPresenter: HelpPresenter,
- private settingRepository: CachedSettingRepository,
+ @inject("CachedSettingRepository") private cachedSettingRepository: CachedSettingRepository,
private bookmarkRepository: BookmarkRepository,
private consoleClient: ConsoleClient,
private contentMessageClient: ContentMessageClient,
@@ -133,7 +133,7 @@ export default class CommandIndicator {
return;
}
const [name, value] = parsers.parseSetOption(keywords);
- await this.settingRepository.setProperty(name, value);
+ await this.cachedSettingRepository.setProperty(name, value);
return this.contentMessageClient.broadcastSettingsChanged();
}
@@ -143,7 +143,7 @@ export default class CommandIndicator {
}
private async urlOrSearch(keywords: string): Promise<any> {
- const settings = await this.settingRepository.get();
+ const settings = await this.cachedSettingRepository.get();
return urls.searchUrl(keywords, settings.search);
}
}
diff --git a/src/background/usecases/CompletionsUseCase.ts b/src/background/usecases/CompletionsUseCase.ts
index 72ba929..9874644 100644
--- a/src/background/usecases/CompletionsUseCase.ts
+++ b/src/background/usecases/CompletionsUseCase.ts
@@ -1,4 +1,4 @@
-import { injectable } from 'tsyringe';
+import { injectable, inject } from 'tsyringe';
import CompletionGroup from '../domains/CompletionGroup';
import CommandDocs from '../domains/CommandDocs';
import CompletionsRepository from '../repositories/CompletionsRepository';
@@ -17,7 +17,7 @@ export default class CompletionsUseCase {
constructor(
private tabPresenter: TabPresenter,
private completionsRepository: CompletionsRepository,
- private settingRepository: CachedSettingRepository,
+ @inject("CachedSettingRepository") private cachedSettingRepository: CachedSettingRepository,
) {
}
@@ -41,7 +41,7 @@ export default class CompletionsUseCase {
// TODO This logic contains view entities. They should be defined on
// content script
- const settings = await this.settingRepository.get();
+ const settings = await this.cachedSettingRepository.get();
const groups: CompletionGroup[] = [];
const complete = settings.properties.complete;
@@ -180,7 +180,7 @@ export default class CompletionsUseCase {
}
async querySearchEngineItems(name: string, keywords: string) {
- const settings = await this.settingRepository.get();
+ const settings = await this.cachedSettingRepository.get();
const engines = Object.keys(settings.search.engines)
.filter(key => key.startsWith(keywords));
return engines.map(key => ({
diff --git a/src/background/usecases/SettingUseCase.ts b/src/background/usecases/SettingUseCase.ts
index 02e1240..69b4572 100644
--- a/src/background/usecases/SettingUseCase.ts
+++ b/src/background/usecases/SettingUseCase.ts
@@ -1,19 +1,18 @@
-import { injectable } from 'tsyringe';
-import LocalSettingRepository from '../repositories/LocalSettingRepository';
+import {inject, injectable} from 'tsyringe';
import CachedSettingRepository from '../repositories/CachedSettingRepository';
-import SettingData, { DefaultSettingData } from '../../shared/SettingData';
+import SettingData, {DefaultSettingData} from '../../shared/SettingData';
import Settings from '../../shared/settings/Settings';
-import NotifyPresenter from '../presenters/NotifyPresenter';
-import SyncSettingRepository from "../repositories/SyncSettingRepository";
+import Notifier from '../presenters/Notifier';
+import SettingRepository from "../repositories/SettingRepository";
@injectable()
export default class SettingUseCase {
constructor(
- private localSettingRepository: LocalSettingRepository,
- private syncSettingRepository: SyncSettingRepository,
- private cachedSettingRepository: CachedSettingRepository,
- private notifyPresenter: NotifyPresenter,
+ @inject("LocalSettingRepository") private localSettingRepository: SettingRepository,
+ @inject("SyncSettingRepository") private syncSettingRepository: SettingRepository,
+ @inject("CachedSettingRepository") private cachedSettingRepository: CachedSettingRepository,
+ @inject("Notifier") private notifier: Notifier,
) {
}
@@ -36,7 +35,7 @@ export default class SettingUseCase {
this.showUnableToLoad(e);
value = DefaultSettingData.toSettings();
}
- this.cachedSettingRepository.update(value!!);
+ await this.cachedSettingRepository.update(value!!);
return value;
}
@@ -54,7 +53,7 @@ export default class SettingUseCase {
private showUnableToLoad(e: Error) {
console.error('unable to load settings', e);
- this.notifyPresenter.notifyInvalidSettings(() => {
+ this.notifier.notifyInvalidSettings(() => {
browser.runtime.openOptionsPage();
});
}
diff --git a/src/background/usecases/VersionUseCase.ts b/src/background/usecases/VersionUseCase.ts
index 645c859..9ea8af9 100644
--- a/src/background/usecases/VersionUseCase.ts
+++ b/src/background/usecases/VersionUseCase.ts
@@ -1,19 +1,19 @@
-import { injectable } from 'tsyringe';
+import { injectable, inject } from 'tsyringe';
import TabPresenter from '../presenters/TabPresenter';
-import NotifyPresenter from '../presenters/NotifyPresenter';
+import Notifier from '../presenters/Notifier';
@injectable()
export default class VersionUseCase {
constructor(
private tabPresenter: TabPresenter,
- private notifyPresenter: NotifyPresenter,
+ @inject("Notifier") private notifier: Notifier,
) {
}
notify(): Promise<void> {
const manifest = browser.runtime.getManifest();
const url = this.releaseNoteUrl(manifest.version);
- return this.notifyPresenter.notifyUpdated(manifest.version, () => {
+ return this.notifier.notifyUpdated(manifest.version, () => {
this.tabPresenter.create(url);
});
}
diff --git a/test/background/usecases/SettingUseCase.test.ts b/test/background/usecases/SettingUseCase.test.ts
new file mode 100644
index 0000000..bfa599c
--- /dev/null
+++ b/test/background/usecases/SettingUseCase.test.ts
@@ -0,0 +1,161 @@
+import "reflect-metadata";
+import SettingUseCase from "../../../src/background/usecases/SettingUseCase";
+import SettingRepository from "../../../src/background/repositories/SettingRepository";
+import SettingData, {JSONTextSettings} from "../../../src/shared/SettingData";
+import CachedSettingRepository from "../../../src/background/repositories/CachedSettingRepository";
+import Settings, {DefaultSetting} from "../../../src/shared/settings/Settings";
+import Notifier from "../../../src/background/presenters/Notifier";
+import {expect} from "chai";
+import Properties from "../../../src/shared/settings/Properties";
+import sinon from 'sinon';
+
+class MockSettingRepository implements SettingRepository {
+ load(): Promise<SettingData | null> {
+ throw new Error("not implemented");
+ }
+
+ onChange(_: () => void): void {
+ }
+}
+
+class MockCachedSettingRepository implements CachedSettingRepository {
+ private current: Settings = DefaultSetting;
+
+ get(): Promise<Settings> {
+ return Promise.resolve(this.current);
+ }
+
+ setProperty(name: string, value: string | number | boolean): Promise<void> {
+ (this.current.properties as any)[name] = value;
+ return Promise.resolve();
+ }
+
+ update(value: Settings): Promise<void> {
+ this.current = value;
+ return Promise.resolve();
+ }
+}
+
+class NopNotifier implements Notifier {
+ notifyInvalidSettings(_onclick: () => void): Promise<void> {
+ return Promise.resolve();
+ }
+
+ notifyUpdated(_version: string, _onclick: () => void): Promise<void> {
+ return Promise.resolve();
+ }
+}
+
+describe('SettingUseCase', () => {
+ let localSettingRepository : SettingRepository;
+ let syncSettingRepository : SettingRepository;
+ let cachedSettingRepository : CachedSettingRepository;
+ let notifier: Notifier;
+ let sut : SettingUseCase;
+
+ beforeEach(() => {
+ localSettingRepository = new MockSettingRepository();
+ syncSettingRepository = new MockSettingRepository();
+ cachedSettingRepository = new MockCachedSettingRepository();
+ notifier = new NopNotifier();
+ sut = new SettingUseCase(
+ localSettingRepository,
+ syncSettingRepository,
+ cachedSettingRepository,
+ notifier
+ );
+ });
+
+ describe('getCached', () => {
+ it("returns cached settings", async () => {
+ const settings = new Settings({
+ keymaps: DefaultSetting.keymaps,
+ search: DefaultSetting.search,
+ blacklist: DefaultSetting.blacklist,
+ properties: new Properties({
+ hintchars: "abcd1234"
+ }),
+ });
+ sinon.stub(cachedSettingRepository, "get")
+ .returns(Promise.resolve(settings));
+
+ const got = await sut.getCached();
+ expect(got.properties.hintchars).to.equal("abcd1234");
+
+ });
+ });
+
+ describe("reload", () => {
+ context("when sync is not set", () => {
+ it("loads settings from local storage", async() => {
+ const settings = new Settings({
+ keymaps: DefaultSetting.keymaps,
+ search: DefaultSetting.search,
+ blacklist: DefaultSetting.blacklist,
+ properties: new Properties({
+ hintchars: "abcd1234"
+ }),
+ });
+ const settingData = SettingData.fromJSON({
+ source: "json",
+ json: JSONTextSettings.fromSettings(settings).toJSONText(),
+ });
+
+ sinon.stub(syncSettingRepository, "load")
+ .returns(Promise.resolve(null));
+ sinon.stub(localSettingRepository, "load")
+ .returns(Promise.resolve(settingData));
+
+ await sut.reload();
+
+ const current = await cachedSettingRepository.get();
+ expect(current.properties.hintchars).to.equal("abcd1234");
+ });
+ });
+
+ context("when local is not set", () => {
+ it("loads settings from sync storage", async() => {
+ const settings = new Settings({
+ keymaps: DefaultSetting.keymaps,
+ search: DefaultSetting.search,
+ blacklist: DefaultSetting.blacklist,
+ properties: new Properties({
+ hintchars: "aaaa1111"
+ }),
+ });
+ const settingData = SettingData.fromJSON({
+ source: "json",
+ json: JSONTextSettings.fromSettings(settings).toJSONText(),
+ });
+
+ sinon.stub(syncSettingRepository, "load")
+ .returns(Promise.resolve(settingData));
+ sinon.stub(localSettingRepository, "load")
+ .returns(Promise.resolve(null));
+
+ await sut.reload();
+
+ const current = await cachedSettingRepository.get();
+ expect(current.properties.hintchars).to.equal("aaaa1111");
+ });
+ });
+
+ context("neither local nor sync not set", () => {
+ it("loads default settings", async() => {
+ it("loads settings from sync storage", async() => {
+ sinon.stub(syncSettingRepository, "load")
+ .returns(Promise.resolve(null));
+ sinon.stub(localSettingRepository, "load")
+ .returns(Promise.resolve(null));
+
+ await sut.reload();
+
+ const current = await cachedSettingRepository.get();
+ expect(current.properties.hintchars).to.equal(DefaultSetting.properties.hintchars);
+ });
+
+ })
+ })
+ })
+});
+