aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--e2e/tab.test.ts6
-rw-r--r--package.json8
-rw-r--r--src/background/clients/NavigateClient.ts14
-rw-r--r--src/background/controllers/OperationController.ts116
-rw-r--r--src/background/di.ts18
-rw-r--r--src/background/infrastructures/ConsoleClient.ts14
-rw-r--r--src/background/operators/Operator.ts5
-rw-r--r--src/background/operators/OperatorFactory.ts6
-rw-r--r--src/background/operators/OperatorFactoryChain.ts6
-rw-r--r--src/background/operators/impls/CancelOperator.ts15
-rw-r--r--src/background/operators/impls/CloseTabOperator.ts22
-rw-r--r--src/background/operators/impls/CloseTabRightOperator.ts21
-rw-r--r--src/background/operators/impls/CommandOperatorFactoryChain.ts63
-rw-r--r--src/background/operators/impls/DuplicateTabOperator.ts11
-rw-r--r--src/background/operators/impls/InternalOpenURLOperator.ts24
-rw-r--r--src/background/operators/impls/InternalOperatorFactoryChain.ts38
-rw-r--r--src/background/operators/impls/NavigateHistoryNextOperator.ts15
-rw-r--r--src/background/operators/impls/NavigateHistoryPrevOperator.ts15
-rw-r--r--src/background/operators/impls/NavigateLinkNextOperator.ts15
-rw-r--r--src/background/operators/impls/NavigateLinkPrevOperator.ts15
-rw-r--r--src/background/operators/impls/NavigateOperatorFactoryChain.ts66
-rw-r--r--src/background/operators/impls/NavigateParentOperator.ts25
-rw-r--r--src/background/operators/impls/NavigateRootOperator.ts12
-rw-r--r--src/background/operators/impls/OpenHomeOperator.ts29
-rw-r--r--src/background/operators/impls/OpenSourceOperator.ts12
-rw-r--r--src/background/operators/impls/OperatorFactoryImpl.ts45
-rw-r--r--src/background/operators/impls/PinTabOperator.ts11
-rw-r--r--src/background/operators/impls/ReloadTabOperator.ts14
-rw-r--r--src/background/operators/impls/ReopenTabOperator.ts10
-rw-r--r--src/background/operators/impls/RepeatLastOperator.ts18
-rw-r--r--src/background/operators/impls/RepeatOperatorFactoryChain.ts29
-rw-r--r--src/background/operators/impls/ResetZoomOperator.ts10
-rw-r--r--src/background/operators/impls/SelectFirstTabOperator.ts11
-rw-r--r--src/background/operators/impls/SelectLastTabOperator.ts11
-rw-r--r--src/background/operators/impls/SelectPreviousSelectedTabOperator.ts14
-rw-r--r--src/background/operators/impls/SelectTabNextOperator.ts19
-rw-r--r--src/background/operators/impls/SelectTabPrevOperator.ts19
-rw-r--r--src/background/operators/impls/ShowAddBookmarkOperator.ts20
-rw-r--r--src/background/operators/impls/ShowBufferCommandOperator.ts16
-rw-r--r--src/background/operators/impls/ShowCommandOperator.ts15
-rw-r--r--src/background/operators/impls/ShowOpenCommandOperator.ts20
-rw-r--r--src/background/operators/impls/ShowTabOpenCommandOperator.ts20
-rw-r--r--src/background/operators/impls/ShowWinOpenCommandOperator.ts20
-rw-r--r--src/background/operators/impls/StartFindOperator.ts15
-rw-r--r--src/background/operators/impls/TabOperatorFactoryChain.ts64
-rw-r--r--src/background/operators/impls/TogglePinnedTabOperator.ts11
-rw-r--r--src/background/operators/impls/UnpinTabOperator.ts11
-rw-r--r--src/background/operators/impls/ZoomInOperator.ts10
-rw-r--r--src/background/operators/impls/ZoomOperatorFactoryChain.ts28
-rw-r--r--src/background/operators/impls/ZoomOutOperator.ts10
-rw-r--r--src/background/presenters/Notifier.ts4
-rw-r--r--src/background/presenters/WindowPresenter.ts11
-rw-r--r--src/background/presenters/ZoomPresenter.ts60
-rw-r--r--src/background/repositories/BrowserSettingRepository.ts6
-rw-r--r--src/background/repositories/RepeatRepository.ts8
-rw-r--r--src/background/usecases/AddonEnabledUseCase.ts2
-rw-r--r--src/background/usecases/CommandUseCase.ts23
-rw-r--r--src/background/usecases/ConsoleUseCase.ts63
-rw-r--r--src/background/usecases/FindUseCase.ts15
-rw-r--r--src/background/usecases/MarkUseCase.ts12
-rw-r--r--src/background/usecases/NavigateUseCase.ts56
-rw-r--r--src/background/usecases/RepeatUseCase.ts7
-rw-r--r--src/background/usecases/TabSelectUseCase.ts51
-rw-r--r--src/background/usecases/TabUseCase.ts102
-rw-r--r--src/background/usecases/ZoomUseCase.ts51
-rw-r--r--src/content/controllers/KeymapController.ts88
-rw-r--r--src/content/di.ts8
-rw-r--r--src/content/hint-key-producer.ts37
-rw-r--r--src/content/operators/Operator.ts5
-rw-r--r--src/content/operators/OperatorFactory.ts6
-rw-r--r--src/content/operators/OperatorFactoryChain.ts6
-rw-r--r--src/content/operators/impls/AbstractScrollOperator.ts10
-rw-r--r--src/content/operators/impls/AddonOperatorFactoryChain.ts40
-rw-r--r--src/content/operators/impls/BackgroundOperationOperator.ts15
-rw-r--r--src/content/operators/impls/ClipboardOperatorFactoryChain.ts47
-rw-r--r--src/content/operators/impls/DisableAddonOperator.ts15
-rw-r--r--src/content/operators/impls/EnableAddonOperator.ts15
-rw-r--r--src/content/operators/impls/EnableJumpMarkOperator.ts10
-rw-r--r--src/content/operators/impls/EnableSetMarkOperator.ts10
-rw-r--r--src/content/operators/impls/FindNextOperator.ts15
-rw-r--r--src/content/operators/impls/FindOperatorFactoryChain.ts25
-rw-r--r--src/content/operators/impls/FindPrevOperator.ts15
-rw-r--r--src/content/operators/impls/FocusOperator.ts10
-rw-r--r--src/content/operators/impls/FocusOperatorFactoryChain.ts22
-rw-r--r--src/content/operators/impls/FollowOperatorFactoryChain.ts27
-rw-r--r--src/content/operators/impls/HorizontalScrollOperator.ts21
-rw-r--r--src/content/operators/impls/MarkOperatorFactoryChain.ts25
-rw-r--r--src/content/operators/impls/OperatorFactoryImpl.ts51
-rw-r--r--src/content/operators/impls/PageScrollOperator.ts21
-rw-r--r--src/content/operators/impls/PasteOperator.ts25
-rw-r--r--src/content/operators/impls/ScrollOperatorFactoryChain.ts68
-rw-r--r--src/content/operators/impls/ScrollToBottomOperator.ts20
-rw-r--r--src/content/operators/impls/ScrollToEndOperator.ts20
-rw-r--r--src/content/operators/impls/ScrollToHomeOperator.ts20
-rw-r--r--src/content/operators/impls/ScrollToTopOperator.ts20
-rw-r--r--src/content/operators/impls/StartFollowOperator.ts14
-rw-r--r--src/content/operators/impls/ToggleAddonOperator.ts16
-rw-r--r--src/content/operators/impls/URLRepository.ts9
-rw-r--r--src/content/operators/impls/VerticalScrollOperator.ts21
-rw-r--r--src/content/operators/impls/YankURLOperator.ts18
-rw-r--r--src/content/presenters/ScrollPresenter.ts2
-rw-r--r--src/content/repositories/HintKeyRepository.ts49
-rw-r--r--src/content/usecases/ClipboardUseCase.ts34
-rw-r--r--src/content/usecases/FindSlaveUseCase.ts17
-rw-r--r--src/content/usecases/FocusUseCase.ts11
-rw-r--r--src/content/usecases/FollowMasterUseCase.ts18
-rw-r--r--src/content/usecases/HintKeyProducer.ts37
-rw-r--r--src/content/usecases/MarkKeyUseCase.ts8
-rw-r--r--src/content/usecases/ScrollUseCase.ts51
-rw-r--r--test/background/mock/MockBrowserSettingRepository.ts10
-rw-r--r--test/background/mock/MockConsoleClient.ts23
-rw-r--r--test/background/mock/MockNavigateClient.ts19
-rw-r--r--test/background/mock/MockRepeatRepository.ts14
-rw-r--r--test/background/mock/MockTabPresenter.ts179
-rw-r--r--test/background/mock/MockWindowPresenter.ts7
-rw-r--r--test/background/mock/MockZoomPresenter.ts15
-rw-r--r--test/background/operators/impls/CancelOperator.test.ts24
-rw-r--r--test/background/operators/impls/CloseTabOperator.test.ts61
-rw-r--r--test/background/operators/impls/CloseTabRightOperator.test.ts24
-rw-r--r--test/background/operators/impls/CommandOperatorFactoryChain.test.ts42
-rw-r--r--test/background/operators/impls/DuplicateTabOperator.test.ts25
-rw-r--r--test/background/operators/impls/InternalOpenURLOperator.test.ts0
-rw-r--r--test/background/operators/impls/InternalOperatorFactoryChain.test.ts37
-rw-r--r--test/background/operators/impls/NavigateHistoryNextOperator.test.ts25
-rw-r--r--test/background/operators/impls/NavigateHistoryPrevOperator.test.ts25
-rw-r--r--test/background/operators/impls/NavigateLinkNextOperator.test.ts22
-rw-r--r--test/background/operators/impls/NavigateLinkPrevOperator.test.ts22
-rw-r--r--test/background/operators/impls/NavigateOperatorFactoryChain.test.ts56
-rw-r--r--test/background/operators/impls/NavigateParentOperator.test.ts53
-rw-r--r--test/background/operators/impls/NavigateRootOperator.test.ts18
-rw-r--r--test/background/operators/impls/OpenHomeOperator.test.ts70
-rw-r--r--test/background/operators/impls/OpenSourceOperator.test.ts21
-rw-r--r--test/background/operators/impls/PinTabOperator.test.ts25
-rw-r--r--test/background/operators/impls/ReloadTabOperator.test.ts34
-rw-r--r--test/background/operators/impls/ReopenTabOperator.test.ts17
-rw-r--r--test/background/operators/impls/RepeatLastOperator.test.ts56
-rw-r--r--test/background/operators/impls/RepeatOperatorFactoryChain.test.ts32
-rw-r--r--test/background/operators/impls/ResetZoomOperator.test.ts17
-rw-r--r--test/background/operators/impls/SelectFirstTabOperator.test.ts20
-rw-r--r--test/background/operators/impls/SelectLastTabOperator.test.ts20
-rw-r--r--test/background/operators/impls/SelectPreviousSelectedTabOperator.test.ts38
-rw-r--r--test/background/operators/impls/SelectTabNextOperator.test.ts35
-rw-r--r--test/background/operators/impls/SelectTabPrevOperator.test.ts35
-rw-r--r--test/background/operators/impls/ShowAddBookmarkOperator.test.ts50
-rw-r--r--test/background/operators/impls/ShowBufferCommandOperator.test.ts25
-rw-r--r--test/background/operators/impls/ShowCommandOperator.test.ts25
-rw-r--r--test/background/operators/impls/ShowOpenCommandOperator.test.ts50
-rw-r--r--test/background/operators/impls/ShowTabOpenCommandOperator.test.ts50
-rw-r--r--test/background/operators/impls/ShowWinOpenCommandOperator.test.ts50
-rw-r--r--test/background/operators/impls/StartFindOperator.test.ts22
-rw-r--r--test/background/operators/impls/TabOperatorFactoryChain.test.ts71
-rw-r--r--test/background/operators/impls/TogglePinnedTabOperator.test.ts32
-rw-r--r--test/background/operators/impls/UnpinTabOperator.test.ts25
-rw-r--r--test/background/operators/impls/ZoomInOperator.test.ts17
-rw-r--r--test/background/operators/impls/ZoomOperatorFactoryChain.test.ts28
-rw-r--r--test/background/operators/impls/ZoomOutOperator.test.ts17
-rw-r--r--test/background/usecases/NavigateUseCase.test.ts183
-rw-r--r--test/content/mock/MockAddonEnabledRepository.ts18
-rw-r--r--test/content/mock/MockAddonIndicatorClient.ts13
-rw-r--r--test/content/mock/MockClipboardRepository.ts16
-rw-r--r--test/content/mock/MockFindMasterClient.ts11
-rw-r--r--test/content/mock/MockFocusPresenter.ts7
-rw-r--r--test/content/mock/MockFollowMasterClient.ts16
-rw-r--r--test/content/mock/MockMarkKeyRepository.ts43
-rw-r--r--test/content/mock/MockOperationClient.ts16
-rw-r--r--test/content/mock/MockScrollPresenter.ts6
-rw-r--r--test/content/mock/MockSettingRepository.ts20
-rw-r--r--test/content/mock/MockURLRepository.ts9
-rw-r--r--test/content/operators/impls/AddonOperatorFactoryChain.test.ts29
-rw-r--r--test/content/operators/impls/BackgroundOperationOperator.test.ts38
-rw-r--r--test/content/operators/impls/ClipboardOperatorFactoryChain.test.ts31
-rw-r--r--test/content/operators/impls/DisableAddonOperator.test.ts19
-rw-r--r--test/content/operators/impls/EnableAddonOperator.test.ts19
-rw-r--r--test/content/operators/impls/EnableJumpMarkOperator.test.ts19
-rw-r--r--test/content/operators/impls/EnableSetMarkOperator.test.ts19
-rw-r--r--test/content/operators/impls/FindNextOperator.test.ts17
-rw-r--r--test/content/operators/impls/FindOperatorFactoryChain.test.ts21
-rw-r--r--test/content/operators/impls/FindPrevOperator.test.ts17
-rw-r--r--test/content/operators/impls/FocusOperator.test.ts17
-rw-r--r--test/content/operators/impls/FocusOperatorFactoryChain.test.ts17
-rw-r--r--test/content/operators/impls/FollowOperatorFactoryChain.test.ts20
-rw-r--r--test/content/operators/impls/HorizontalScrollOperator.test.ts28
-rw-r--r--test/content/operators/impls/MarkOperatorFactoryChain.test.ts21
-rw-r--r--test/content/operators/impls/PageScrollOperator.test.ts28
-rw-r--r--test/content/operators/impls/PasteOperator.test.ts51
-rw-r--r--test/content/operators/impls/ScrollOperatorFactoryChain.test.ts46
-rw-r--r--test/content/operators/impls/ScrollToBottomOperator.test.ts18
-rw-r--r--test/content/operators/impls/ScrollToEndOperator.test.ts18
-rw-r--r--test/content/operators/impls/ScrollToHomeOperator.test.ts18
-rw-r--r--test/content/operators/impls/ScrollToTopOperator.test.ts18
-rw-r--r--test/content/operators/impls/StartFollowOperator.test.ts20
-rw-r--r--test/content/operators/impls/ToggleAddonOperator.test.ts24
-rw-r--r--test/content/operators/impls/VerticalScrollOperator.test.ts28
-rw-r--r--test/content/operators/impls/YankURLOperator.test.ts26
-rw-r--r--test/content/usecases/ClipboardUseCase.test.ts95
-rw-r--r--test/content/usecases/HintKeyProducer.test.ts33
-rw-r--r--yarn.lock387
197 files changed, 4427 insertions, 1211 deletions
diff --git a/e2e/tab.test.ts b/e2e/tab.test.ts
index 98ec9f8..49d1c03 100644
--- a/e2e/tab.test.ts
+++ b/e2e/tab.test.ts
@@ -79,8 +79,10 @@ describe("tab test", () => {
const page = await Page.currentContext(webdriver);
await page.sendKeys("x", "$");
- const current = await browser.tabs.query({ windowId: win.id });
- assert.strictEqual(current.length, 2);
+ await eventually(async () => {
+ const current = await browser.tabs.query({ windowId: win.id });
+ assert.strictEqual(current.length, 2);
+ });
});
it("duplicates tab by zd", async () => {
diff --git a/package.json b/package.json
index 8a3aeeb..b1c9eae 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"scripts": {
"schema": "ajv compile -s src/shared/settings/schema.json -o src/shared/settings/validate.js",
"start": "webpack --mode development -w --debug --devtool inline-source-map",
- "build": "NODE_ENV=production webpack --mode production --progress --display-error-details --devtool inline-source-map",
+ "build": "NODE_ENV=production webpack --mode production --progress --devtool inline-source-map",
"package": "yarn build && script/package",
"lint": "eslint --ext .ts,.tsx .",
"lint:fix": "eslint --ext .ts,.tsx . --fix",
@@ -62,7 +62,7 @@
"karma-webpack": "^4.0.2",
"lanthan": "0.0.2",
"mocha": "^8.1.1",
- "prettier": "2.2.0",
+ "prettier": "2.2.1",
"prettier-eslint": "11.0.0",
"react": "16.13.1",
"react-dom": "16.13.1",
@@ -82,7 +82,7 @@
"typescript": "4.0.3",
"web-ext-types": "^3.2.1",
"webextensions-api-fake": "^0.9.1",
- "webpack": "4.44.1",
- "webpack-cli": "3.3.12"
+ "webpack": "4.44.2",
+ "webpack-cli": "4.2.0"
}
}
diff --git a/src/background/clients/NavigateClient.ts b/src/background/clients/NavigateClient.ts
index 40ceb45..af8688e 100644
--- a/src/background/clients/NavigateClient.ts
+++ b/src/background/clients/NavigateClient.ts
@@ -1,8 +1,16 @@
-import { injectable } from "tsyringe";
import * as messages from "../../shared/messages";
-@injectable()
-export default class NavigateClient {
+export default interface NavigateClient {
+ historyNext(tabId: number): Promise<void>;
+
+ historyPrev(tabId: number): Promise<void>;
+
+ linkNext(tabId: number): Promise<void>;
+
+ linkPrev(tabId: number): Promise<void>;
+}
+
+export class NavigateClientImpl implements NavigateClient {
async historyNext(tabId: number): Promise<void> {
await browser.tabs.sendMessage(tabId, {
type: messages.NAVIGATE_HISTORY_NEXT,
diff --git a/src/background/controllers/OperationController.ts b/src/background/controllers/OperationController.ts
index 5a7047d..d606ac0 100644
--- a/src/background/controllers/OperationController.ts
+++ b/src/background/controllers/OperationController.ts
@@ -1,23 +1,14 @@
-import { injectable } from "tsyringe";
+import { inject, injectable } from "tsyringe";
import * as operations from "../../shared/operations";
-import FindUseCase from "../usecases/FindUseCase";
-import ConsoleUseCase from "../usecases/ConsoleUseCase";
-import TabUseCase from "../usecases/TabUseCase";
-import TabSelectUseCase from "../usecases/TabSelectUseCase";
-import ZoomUseCase from "../usecases/ZoomUseCase";
-import NavigateUseCase from "../usecases/NavigateUseCase";
+import OperatorFactory from "../operators/OperatorFactory";
import RepeatUseCase from "../usecases/RepeatUseCase";
@injectable()
export default class OperationController {
constructor(
- private findUseCase: FindUseCase,
- private consoleUseCase: ConsoleUseCase,
- private tabUseCase: TabUseCase,
- private tabSelectUseCase: TabSelectUseCase,
- private zoomUseCase: ZoomUseCase,
- private navigateUseCase: NavigateUseCase,
- private repeatUseCase: RepeatUseCase
+ private readonly repeatUseCase: RepeatUseCase,
+ @inject("OperatorFactory")
+ private readonly operatorFactory: OperatorFactory
) {}
async exec(repeat: number, op: operations.Operation): Promise<any> {
@@ -27,105 +18,14 @@ export default class OperationController {
}
}
- // eslint-disable-next-line complexity, max-lines-per-function
- async doOperation(
+ private async doOperation(
repeat: number,
operation: operations.Operation
): Promise<any> {
- // eslint-disable-next-line complexity, max-lines-per-function
- const opFunc = (() => {
- switch (operation.type) {
- case operations.TAB_CLOSE:
- return () =>
- this.tabUseCase.close(false, operation.select === "left");
- case operations.TAB_CLOSE_RIGHT:
- return () => this.tabUseCase.closeRight();
- case operations.TAB_CLOSE_FORCE:
- return () => this.tabUseCase.close(true);
- case operations.TAB_REOPEN:
- return () => this.tabUseCase.reopen();
- case operations.TAB_PREV:
- return () => this.tabSelectUseCase.selectPrev(1);
- case operations.TAB_NEXT:
- return () => this.tabSelectUseCase.selectNext(1);
- case operations.TAB_FIRST:
- return () => this.tabSelectUseCase.selectFirst();
- case operations.TAB_LAST:
- return () => this.tabSelectUseCase.selectLast();
- case operations.TAB_PREV_SEL:
- return () => this.tabSelectUseCase.selectPrevSelected();
- case operations.TAB_RELOAD:
- return () => this.tabUseCase.reload(operation.cache);
- case operations.TAB_PIN:
- return () => this.tabUseCase.setPinned(true);
- case operations.TAB_UNPIN:
- return () => this.tabUseCase.setPinned(false);
- case operations.TAB_TOGGLE_PINNED:
- return () => this.tabUseCase.togglePinned();
- case operations.TAB_DUPLICATE:
- return () => this.tabUseCase.duplicate();
- case operations.PAGE_SOURCE:
- return () => this.tabUseCase.openPageSource();
- case operations.PAGE_HOME:
- return () => this.tabUseCase.openHome(operation.newTab);
- case operations.ZOOM_IN:
- return () => this.zoomUseCase.zoomIn();
- case operations.ZOOM_OUT:
- return () => this.zoomUseCase.zoomOut();
- case operations.ZOOM_NEUTRAL:
- return () => this.zoomUseCase.zoomNutoral();
- case operations.COMMAND_SHOW:
- return () => this.consoleUseCase.showCommand();
- case operations.COMMAND_SHOW_OPEN:
- return () => this.consoleUseCase.showOpenCommand(operation.alter);
- case operations.COMMAND_SHOW_TABOPEN:
- return () => this.consoleUseCase.showTabopenCommand(operation.alter);
- case operations.COMMAND_SHOW_WINOPEN:
- return () => this.consoleUseCase.showWinopenCommand(operation.alter);
- case operations.COMMAND_SHOW_BUFFER:
- return () => this.consoleUseCase.showBufferCommand();
- case operations.COMMAND_SHOW_ADDBOOKMARK:
- return () =>
- this.consoleUseCase.showAddbookmarkCommand(operation.alter);
- case operations.FIND_START:
- return () => this.findUseCase.findStart();
- case operations.CANCEL:
- return () => this.consoleUseCase.hideConsole();
- case operations.NAVIGATE_HISTORY_PREV:
- return () => this.navigateUseCase.openHistoryPrev();
- case operations.NAVIGATE_HISTORY_NEXT:
- return () => this.navigateUseCase.openHistoryNext();
- case operations.NAVIGATE_LINK_PREV:
- return () => this.navigateUseCase.openLinkPrev();
- case operations.NAVIGATE_LINK_NEXT:
- return () => this.navigateUseCase.openLinkNext();
- case operations.NAVIGATE_PARENT:
- return () => this.navigateUseCase.openParent();
- case operations.NAVIGATE_ROOT:
- return () => this.navigateUseCase.openRoot();
- case operations.REPEAT_LAST:
- return () => {
- const last = this.repeatUseCase.getLastOperation();
- if (typeof last !== "undefined") {
- return this.doOperation(1, last);
- }
- return Promise.resolve();
- };
- case operations.INTERNAL_OPEN_URL:
- return () =>
- this.tabUseCase.openURL(
- operation.url,
- operation.newTab,
- operation.newWindow
- );
- default:
- throw new Error("unknown operation: " + operation.type);
- }
- })();
-
+ const operator = this.operatorFactory.create(operation);
for (let i = 0; i < repeat; ++i) {
// eslint-disable-next-line no-await-in-loop
- await opFunc();
+ await operator.run();
}
}
}
diff --git a/src/background/di.ts b/src/background/di.ts
index 5e6ad03..358ff1e 100644
--- a/src/background/di.ts
+++ b/src/background/di.ts
@@ -11,9 +11,16 @@ import HistoryRepositoryImpl from "./completion/impl/HistoryRepositoryImpl";
import BookmarkRepositoryImpl from "./completion/impl/BookmarkRepositoryImpl";
import TabRepositoryImpl from "./completion/impl/TabRepositoryImpl";
import { TabPresenterImpl } from "./presenters/TabPresenter";
+import { OperatorFactoryImpl } from "./operators/impls/OperatorFactoryImpl";
+import { NavigateClientImpl } from "./clients/NavigateClient";
+import { ConsoleClientImpl } from "./infrastructures/ConsoleClient";
+import { BrowserSettingRepositoryImpl } from "./repositories/BrowserSettingRepository";
+import { RepeatRepositoryImpl } from "./repositories/RepeatRepository";
+import { ZoomPresenterImpl } from "./presenters/ZoomPresenter";
+import { WindowPresenterImpl } from "./presenters/WindowPresenter";
container.register("LocalSettingRepository", {
- useValue: LocalSettingRepository,
+ useClass: LocalSettingRepository,
});
container.register("SyncSettingRepository", {
useClass: SyncSettingRepository,
@@ -24,5 +31,14 @@ container.register("CachedSettingRepository", {
container.register("Notifier", { useClass: NotifierImpl });
container.register("HistoryRepository", { useClass: HistoryRepositoryImpl });
container.register("BookmarkRepository", { useClass: BookmarkRepositoryImpl });
+container.register("BrowserSettingRepository", {
+ useClass: BrowserSettingRepositoryImpl,
+});
+container.register("RepeatRepository", { useClass: RepeatRepositoryImpl });
container.register("TabRepository", { useClass: TabRepositoryImpl });
+container.register("ZoomPresenter", { useClass: ZoomPresenterImpl });
container.register("TabPresenter", { useClass: TabPresenterImpl });
+container.register("WindowPresenter", { useClass: WindowPresenterImpl });
+container.register("NavigateClient", { useClass: NavigateClientImpl });
+container.register("ConsoleClient", { useClass: ConsoleClientImpl });
+container.register("OperatorFactory", { useClass: OperatorFactoryImpl });
diff --git a/src/background/infrastructures/ConsoleClient.ts b/src/background/infrastructures/ConsoleClient.ts
index 8d0af89..2a1df5b 100644
--- a/src/background/infrastructures/ConsoleClient.ts
+++ b/src/background/infrastructures/ConsoleClient.ts
@@ -1,8 +1,20 @@
import { injectable } from "tsyringe";
import * as messages from "../../shared/messages";
+export default interface ConsoleClient {
+ showCommand(tabId: number, command: string): Promise<any>;
+
+ showFind(tabId: number): Promise<any>;
+
+ showInfo(tabId: number, message: string): Promise<any>;
+
+ showError(tabId: number, message: string): Promise<any>;
+
+ hide(tabId: number): Promise<any>;
+}
+
@injectable()
-export default class ConsoleClient {
+export class ConsoleClientImpl implements ConsoleClient {
showCommand(tabId: number, command: string): Promise<any> {
return browser.tabs.sendMessage(tabId, {
type: messages.CONSOLE_SHOW_COMMAND,
diff --git a/src/background/operators/Operator.ts b/src/background/operators/Operator.ts
new file mode 100644
index 0000000..3b1fe03
--- /dev/null
+++ b/src/background/operators/Operator.ts
@@ -0,0 +1,5 @@
+interface Operator {
+ run(): Promise<void>;
+}
+
+export default Operator;
diff --git a/src/background/operators/OperatorFactory.ts b/src/background/operators/OperatorFactory.ts
new file mode 100644
index 0000000..9a5234c
--- /dev/null
+++ b/src/background/operators/OperatorFactory.ts
@@ -0,0 +1,6 @@
+import Operator from "./Operator";
+import { Operation } from "../../shared/operations";
+
+export default interface OperatorFactory {
+ create(op: Operation): Operator;
+}
diff --git a/src/background/operators/OperatorFactoryChain.ts b/src/background/operators/OperatorFactoryChain.ts
new file mode 100644
index 0000000..046ed42
--- /dev/null
+++ b/src/background/operators/OperatorFactoryChain.ts
@@ -0,0 +1,6 @@
+import Operator from "./Operator";
+import { Operation } from "../../shared/operations";
+
+export default interface OperatorFactoryChain {
+ create(op: Operation): Operator | null;
+}
diff --git a/src/background/operators/impls/CancelOperator.ts b/src/background/operators/impls/CancelOperator.ts
new file mode 100644
index 0000000..7ab09fb
--- /dev/null
+++ b/src/background/operators/impls/CancelOperator.ts
@@ -0,0 +1,15 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+import ConsoleClient from "../../infrastructures/ConsoleClient";
+
+export default class CancelOperator implements Operator {
+ constructor(
+ private readonly tabPresenter: TabPresenter,
+ private readonly consoleClient: ConsoleClient
+ ) {}
+
+ async run(): Promise<void> {
+ const tab = await this.tabPresenter.getCurrent();
+ return this.consoleClient.hide(tab.id as number);
+ }
+}
diff --git a/src/background/operators/impls/CloseTabOperator.ts b/src/background/operators/impls/CloseTabOperator.ts
new file mode 100644
index 0000000..5d8e80b
--- /dev/null
+++ b/src/background/operators/impls/CloseTabOperator.ts
@@ -0,0 +1,22 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+
+export default class CloseTabOperator implements Operator {
+ constructor(
+ private readonly tabPresenter: TabPresenter,
+ private readonly force: boolean = false,
+ private readonly selectLeft: boolean = false
+ ) {}
+
+ async run(): Promise<void> {
+ const tab = await this.tabPresenter.getCurrent();
+ if (!this.force && tab.pinned) {
+ return Promise.resolve();
+ }
+ if (this.selectLeft && tab.index > 0) {
+ const tabs = await this.tabPresenter.getAll();
+ await this.tabPresenter.select(tabs[tab.index - 1].id as number);
+ }
+ return this.tabPresenter.remove([tab.id as number]);
+ }
+}
diff --git a/src/background/operators/impls/CloseTabRightOperator.ts b/src/background/operators/impls/CloseTabRightOperator.ts
new file mode 100644
index 0000000..f36930e
--- /dev/null
+++ b/src/background/operators/impls/CloseTabRightOperator.ts
@@ -0,0 +1,21 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+
+export default class CloseTabRightOperator implements Operator {
+ constructor(private readonly tabPresenter: TabPresenter) {}
+
+ async run(): Promise<void> {
+ const tabs = await this.tabPresenter.getAll();
+ tabs.sort((t1, t2) => t1.index - t2.index);
+ const index = tabs.findIndex((t) => t.active);
+ if (index < 0) {
+ return;
+ }
+ for (let i = index + 1; i < tabs.length; ++i) {
+ const tab = tabs[i];
+ if (!tab.pinned) {
+ await this.tabPresenter.remove([tab.id as number]);
+ }
+ }
+ }
+}
diff --git a/src/background/operators/impls/CommandOperatorFactoryChain.ts b/src/background/operators/impls/CommandOperatorFactoryChain.ts
new file mode 100644
index 0000000..680a384
--- /dev/null
+++ b/src/background/operators/impls/CommandOperatorFactoryChain.ts
@@ -0,0 +1,63 @@
+import { inject, injectable } from "tsyringe";
+import Operator from "../Operator";
+import OperatorFactoryChain from "../OperatorFactoryChain";
+import ShowCommandOperator from "./ShowCommandOperator";
+import ShowOpenCommandOperator from "./ShowOpenCommandOperator";
+import ShowTabOpenCommandOperator from "./ShowTabOpenCommandOperator";
+import ShowWinOpenCommandOperator from "./ShowWinOpenCommandOperator";
+import ShowBufferCommandOperator from "./ShowBufferCommandOperator";
+import ShowAddBookmarkOperator from "./ShowAddBookmarkOperator";
+import TabPresenter from "../../presenters/TabPresenter";
+import ConsoleClient from "../../infrastructures/ConsoleClient";
+import * as operations from "../../../shared/operations";
+import StartFindOperator from "./StartFindOperator";
+
+@injectable()
+export default class CommandOperatorFactoryChain
+ implements OperatorFactoryChain {
+ constructor(
+ @inject("TabPresenter")
+ private readonly tabPresenter: TabPresenter,
+ @inject("ConsoleClient")
+ private readonly consoleClient: ConsoleClient
+ ) {}
+
+ create(op: operations.Operation): Operator | null {
+ switch (op.type) {
+ case operations.COMMAND_SHOW:
+ return new ShowCommandOperator(this.tabPresenter, this.consoleClient);
+ case operations.COMMAND_SHOW_OPEN:
+ return new ShowOpenCommandOperator(
+ this.tabPresenter,
+ this.consoleClient,
+ op.alter
+ );
+ case operations.COMMAND_SHOW_TABOPEN:
+ return new ShowTabOpenCommandOperator(
+ this.tabPresenter,
+ this.consoleClient,
+ op.alter
+ );
+ case operations.COMMAND_SHOW_WINOPEN:
+ return new ShowWinOpenCommandOperator(
+ this.tabPresenter,
+ this.consoleClient,
+ op.alter
+ );
+ case operations.COMMAND_SHOW_BUFFER:
+ return new ShowBufferCommandOperator(
+ this.tabPresenter,
+ this.consoleClient
+ );
+ case operations.COMMAND_SHOW_ADDBOOKMARK:
+ return new ShowAddBookmarkOperator(
+ this.tabPresenter,
+ this.consoleClient,
+ op.alter
+ );
+ case operations.FIND_START:
+ return new StartFindOperator(this.tabPresenter, this.consoleClient);
+ }
+ return null;
+ }
+}
diff --git a/src/background/operators/impls/DuplicateTabOperator.ts b/src/background/operators/impls/DuplicateTabOperator.ts
new file mode 100644
index 0000000..7737cfa
--- /dev/null
+++ b/src/background/operators/impls/DuplicateTabOperator.ts
@@ -0,0 +1,11 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+
+export default class DuplicateTabOperator implements Operator {
+ constructor(private readonly tabPresenter: TabPresenter) {}
+
+ async run(): Promise<void> {
+ const tab = await this.tabPresenter.getCurrent();
+ await this.tabPresenter.duplicate(tab.id as number);
+ }
+}
diff --git a/src/background/operators/impls/InternalOpenURLOperator.ts b/src/background/operators/impls/InternalOpenURLOperator.ts
new file mode 100644
index 0000000..6bf513b
--- /dev/null
+++ b/src/background/operators/impls/InternalOpenURLOperator.ts
@@ -0,0 +1,24 @@
+import Operator from "../Operator";
+import WindowPresenter from "../../presenters/WindowPresenter";
+import TabPresenter from "../../presenters/TabPresenter";
+
+export default class InternalOpenURLOperator implements Operator {
+ constructor(
+ private readonly windowPresenter: WindowPresenter,
+ private readonly tabPresenter: TabPresenter,
+ private readonly url: string,
+ private readonly newTab?: boolean,
+ private readonly newWindow?: boolean
+ ) {}
+
+ async run(): Promise<void> {
+ if (this.newWindow) {
+ await this.windowPresenter.create(this.url);
+ } else if (this.newTab) {
+ await this.tabPresenter.create(this.url);
+ } else {
+ const tab = await this.tabPresenter.getCurrent();
+ await this.tabPresenter.open(this.url, tab.id);
+ }
+ }
+}
diff --git a/src/background/operators/impls/InternalOperatorFactoryChain.ts b/src/background/operators/impls/InternalOperatorFactoryChain.ts
new file mode 100644
index 0000000..05615f6
--- /dev/null
+++ b/src/background/operators/impls/InternalOperatorFactoryChain.ts
@@ -0,0 +1,38 @@
+import { inject, injectable } from "tsyringe";
+import Operator from "../Operator";
+import OperatorFactoryChain from "../OperatorFactoryChain";
+import CancelOperator from "./CancelOperator";
+import InternalOpenURLOperator from "./InternalOpenURLOperator";
+import TabPresenter from "../../presenters/TabPresenter";
+import ConsoleClient from "../../infrastructures/ConsoleClient";
+import WindowPresenter from "../../presenters/WindowPresenter";
+import * as operations from "../../../shared/operations";
+
+@injectable()
+export default class InternalOperatorFactoryChain
+ implements OperatorFactoryChain {
+ constructor(
+ @inject("WindowPresenter")
+ private readonly windowPresenter: WindowPresenter,
+ @inject("TabPresenter")
+ private readonly tabPresenter: TabPresenter,
+ @inject("ConsoleClient")
+ private readonly consoleClient: ConsoleClient
+ ) {}
+
+ create(op: operations.Operation): Operator | null {
+ switch (op.type) {
+ case operations.CANCEL:
+ return new CancelOperator(this.tabPresenter, this.consoleClient);
+ case operations.INTERNAL_OPEN_URL:
+ return new InternalOpenURLOperator(
+ this.windowPresenter,
+ this.tabPresenter,
+ op.url,
+ op.newTab,
+ op.newWindow
+ );
+ }
+ return null;
+ }
+}
diff --git a/src/background/operators/impls/NavigateHistoryNextOperator.ts b/src/background/operators/impls/NavigateHistoryNextOperator.ts
new file mode 100644
index 0000000..b092c48
--- /dev/null
+++ b/src/background/operators/impls/NavigateHistoryNextOperator.ts
@@ -0,0 +1,15 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+import NavigateClient from "../../clients/NavigateClient";
+
+export default class NavigateHistoryNextOperator implements Operator {
+ constructor(
+ private readonly tabPresenter: TabPresenter,
+ private readonly navigateClient: NavigateClient
+ ) {}
+
+ async run(): Promise<void> {
+ const tab = await this.tabPresenter.getCurrent();
+ await this.navigateClient.historyNext(tab.id!);
+ }
+}
diff --git a/src/background/operators/impls/NavigateHistoryPrevOperator.ts b/src/background/operators/impls/NavigateHistoryPrevOperator.ts
new file mode 100644
index 0000000..27d4ee9
--- /dev/null
+++ b/src/background/operators/impls/NavigateHistoryPrevOperator.ts
@@ -0,0 +1,15 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+import NavigateClient from "../../clients/NavigateClient";
+
+export default class NavigateHistoryPrevOperator implements Operator {
+ constructor(
+ private readonly tabPresenter: TabPresenter,
+ private readonly navigateClient: NavigateClient
+ ) {}
+
+ async run(): Promise<void> {
+ const tab = await this.tabPresenter.getCurrent();
+ await this.navigateClient.historyPrev(tab.id!);
+ }
+}
diff --git a/src/background/operators/impls/NavigateLinkNextOperator.ts b/src/background/operators/impls/NavigateLinkNextOperator.ts
new file mode 100644
index 0000000..dbbcc45
--- /dev/null
+++ b/src/background/operators/impls/NavigateLinkNextOperator.ts
@@ -0,0 +1,15 @@
+import Operator from "../Operator";
+import NavigateClient from "../../clients/NavigateClient";
+import TabPresenter from "../../presenters/TabPresenter";
+
+export default class NavigateLinkNextOperator implements Operator {
+ constructor(
+ private readonly tabPresenter: TabPresenter,
+ private readonly navigateClient: NavigateClient
+ ) {}
+
+ async run(): Promise<void> {
+ const tab = await this.tabPresenter.getCurrent();
+ await this.navigateClient.linkNext(tab.id!);
+ }
+}
diff --git a/src/background/operators/impls/NavigateLinkPrevOperator.ts b/src/background/operators/impls/NavigateLinkPrevOperator.ts
new file mode 100644
index 0000000..fe41ee6
--- /dev/null
+++ b/src/background/operators/impls/NavigateLinkPrevOperator.ts
@@ -0,0 +1,15 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+import NavigateClient from "../../clients/NavigateClient";
+
+export default class NavigateLinkPrevOperator implements Operator {
+ constructor(
+ private readonly tabPresenter: TabPresenter,
+ private readonly navigateClient: NavigateClient
+ ) {}
+
+ async run(): Promise<void> {
+ const tab = await this.tabPresenter.getCurrent();
+ await this.navigateClient.linkPrev(tab.id!);
+ }
+}
diff --git a/src/background/operators/impls/NavigateOperatorFactoryChain.ts b/src/background/operators/impls/NavigateOperatorFactoryChain.ts
new file mode 100644
index 0000000..618db45
--- /dev/null
+++ b/src/background/operators/impls/NavigateOperatorFactoryChain.ts
@@ -0,0 +1,66 @@
+import { inject, injectable } from "tsyringe";
+import Operator from "../Operator";
+import OperatorFactoryChain from "../OperatorFactoryChain";
+import NavigateHistoryPrevOperator from "./NavigateHistoryPrevOperator";
+import NavigateHistoryNextOperator from "./NavigateHistoryNextOperator";
+import NavigateLinkPrevOperator from "./NavigateLinkPrevOperator";
+import NavigateLinkNextOperator from "./NavigateLinkNextOperator";
+import NavigateParentOperator from "./NavigateParentOperator";
+import NavigateRootOperator from "./NavigateRootOperator";
+import OpenSourceOperator from "./OpenSourceOperator";
+import OpenHomeOperator from "./OpenHomeOperator";
+import TabPresenter from "../../presenters/TabPresenter";
+import NavigateClient from "../../clients/NavigateClient";
+import BrowserSettingRepository from "../../repositories/BrowserSettingRepository";
+import * as operations from "../../../shared/operations";
+
+@injectable()
+export default class NavigateOperatorFactoryChain
+ implements OperatorFactoryChain {
+ constructor(
+ @inject("TabPresenter")
+ private readonly tabPresenter: TabPresenter,
+ @inject("NavigateClient")
+ private readonly navigateClient: NavigateClient,
+ @inject("BrowserSettingRepository")
+ private readonly browserSettingRepository: BrowserSettingRepository
+ ) {}
+
+ create(op: operations.Operation): Operator | null {
+ switch (op.type) {
+ case operations.NAVIGATE_HISTORY_PREV:
+ return new NavigateHistoryPrevOperator(
+ this.tabPresenter,
+ this.navigateClient
+ );
+ case operations.NAVIGATE_HISTORY_NEXT:
+ return new NavigateHistoryNextOperator(
+ this.tabPresenter,
+ this.navigateClient
+ );
+ case operations.NAVIGATE_LINK_PREV:
+ return new NavigateLinkPrevOperator(
+ this.tabPresenter,
+ this.navigateClient
+ );
+ case operations.NAVIGATE_LINK_NEXT:
+ return new NavigateLinkNextOperator(
+ this.tabPresenter,
+ this.navigateClient
+ );
+ case operations.NAVIGATE_PARENT:
+ return new NavigateParentOperator(this.tabPresenter);
+ case operations.NAVIGATE_ROOT:
+ return new NavigateRootOperator(this.tabPresenter);
+ case operations.PAGE_SOURCE:
+ return new OpenSourceOperator(this.tabPresenter);
+ case operations.PAGE_HOME:
+ return new OpenHomeOperator(
+ this.tabPresenter,
+ this.browserSettingRepository,
+ op.newTab
+ );
+ }
+ return null;
+ }
+}
diff --git a/src/background/operators/impls/NavigateParentOperator.ts b/src/background/operators/impls/NavigateParentOperator.ts
new file mode 100644
index 0000000..652cfb8
--- /dev/null
+++ b/src/background/operators/impls/NavigateParentOperator.ts
@@ -0,0 +1,25 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+
+export default class NavigateParentOperator implements Operator {
+ constructor(private readonly tabPresenter: TabPresenter) {}
+
+ async run(): Promise<void> {
+ const tab = await this.tabPresenter.getCurrent();
+ const url = new URL(tab.url!);
+ if (url.hash.length > 0) {
+ url.hash = "";
+ } else if (url.search.length > 0) {
+ url.search = "";
+ } else {
+ const basenamePattern = /\/[^/]+$/;
+ const lastDirPattern = /\/[^/]+\/$/;
+ if (basenamePattern.test(url.pathname)) {
+ url.pathname = url.pathname.replace(basenamePattern, "/");
+ } else if (lastDirPattern.test(url.pathname)) {
+ url.pathname = url.pathname.replace(lastDirPattern, "/");
+ }
+ }
+ await this.tabPresenter.open(url.href);
+ }
+}
diff --git a/src/background/operators/impls/NavigateRootOperator.ts b/src/background/operators/impls/NavigateRootOperator.ts
new file mode 100644
index 0000000..b140156
--- /dev/null
+++ b/src/background/operators/impls/NavigateRootOperator.ts
@@ -0,0 +1,12 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+
+export default class NavigateRootOperator implements Operator {
+ constructor(private readonly tabPresenter: TabPresenter) {}
+
+ async run(): Promise<void> {
+ const tab = await this.tabPresenter.getCurrent();
+ const url = new URL(tab.url!);
+ await this.tabPresenter.open(url.origin);
+ }
+}
diff --git a/src/background/operators/impls/OpenHomeOperator.ts b/src/background/operators/impls/OpenHomeOperator.ts
new file mode 100644
index 0000000..4773be6
--- /dev/null
+++ b/src/background/operators/impls/OpenHomeOperator.ts
@@ -0,0 +1,29 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+import BrowserSettingRepository from "../../repositories/BrowserSettingRepository";
+
+export default class OpenHomeOperator implements Operator {
+ constructor(
+ private readonly tabPresenter: TabPresenter,
+ private readonly browserSettingRepository: BrowserSettingRepository,
+ private readonly newTab: boolean
+ ) {}
+
+ async run(): Promise<void> {
+ const tab = await this.tabPresenter.getCurrent();
+ const urls = await this.browserSettingRepository.getHomepageUrls();
+ if (urls.length === 1 && urls[0] === "about:home") {
+ // eslint-disable-next-line max-len
+ throw new Error(
+ "Cannot open Firefox Home (about:home) by WebExtensions, set your custom URLs"
+ );
+ }
+ if (urls.length === 1 && !this.newTab) {
+ await this.tabPresenter.open(urls[0], tab.id);
+ return;
+ }
+ for (const url of urls) {
+ await this.tabPresenter.create(url);
+ }
+ }
+}
diff --git a/src/background/operators/impls/OpenSourceOperator.ts b/src/background/operators/impls/OpenSourceOperator.ts
new file mode 100644
index 0000000..9185ba5
--- /dev/null
+++ b/src/background/operators/impls/OpenSourceOperator.ts
@@ -0,0 +1,12 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+
+export default class OpenSourceOperator implements Operator {
+ constructor(private readonly tabPresenter: TabPresenter) {}
+
+ async run(): Promise<void> {
+ const tab = await this.tabPresenter.getCurrent();
+ const url = "view-source:" + tab.url;
+ await this.tabPresenter.create(url);
+ }
+}
diff --git a/src/background/operators/impls/OperatorFactoryImpl.ts b/src/background/operators/impls/OperatorFactoryImpl.ts
new file mode 100644
index 0000000..34e7bb5
--- /dev/null
+++ b/src/background/operators/impls/OperatorFactoryImpl.ts
@@ -0,0 +1,45 @@
+import { inject, delay, injectable } from "tsyringe";
+import Operator from "../Operator";
+import OperatorFactory from "../OperatorFactory";
+import OperatorFactoryChain from "../OperatorFactoryChain";
+import CommandOperatorFactoryChain from "./CommandOperatorFactoryChain";
+import InternalOperatorFactoryChain from "./InternalOperatorFactoryChain";
+import NavigateOperatorFactoryChain from "./NavigateOperatorFactoryChain";
+import RepeatOperatorFactoryChain from "./RepeatOperatorFactoryChain";
+import TabOperatorFactoryChain from "./TabOperatorFactoryChain";
+import ZoomOperatorFactoryChain from "./ZoomOperatorFactoryChain";
+import * as operations from "../../../shared/operations";
+
+@injectable()
+export class OperatorFactoryImpl implements OperatorFactory {
+ private readonly factoryChains: OperatorFactoryChain[];
+
+ constructor(
+ commandOperatorFactoryChain: CommandOperatorFactoryChain,
+ internalOperatorFactoryChain: InternalOperatorFactoryChain,
+ navigateOperatorFactoryChain: NavigateOperatorFactoryChain,
+ tabOperatorFactoryChain: TabOperatorFactoryChain,
+ zoomOperatorFactoryChain: ZoomOperatorFactoryChain,
+ @inject(delay(() => RepeatOperatorFactoryChain))
+ repeatOperatorFactoryChain: RepeatOperatorFactoryChain
+ ) {
+ this.factoryChains = [
+ commandOperatorFactoryChain,
+ internalOperatorFactoryChain,
+ navigateOperatorFactoryChain,
+ repeatOperatorFactoryChain,
+ tabOperatorFactoryChain,
+ zoomOperatorFactoryChain,
+ ];
+ }
+
+ create(op: operations.Operation): Operator {
+ for (const chain of this.factoryChains) {
+ const operator = chain.create(op);
+ if (operator !== null) {
+ return operator;
+ }
+ }
+ throw new Error("unknown operation: " + op.type);
+ }
+}
diff --git a/src/background/operators/impls/PinTabOperator.ts b/src/background/operators/impls/PinTabOperator.ts
new file mode 100644
index 0000000..8121725
--- /dev/null
+++ b/src/background/operators/impls/PinTabOperator.ts
@@ -0,0 +1,11 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+
+export default class PinTabOperator implements Operator {
+ constructor(private readonly tabPresenter: TabPresenter) {}
+
+ async run(): Promise<void> {
+ const tab = await this.tabPresenter.getCurrent();
+ return this.tabPresenter.setPinned(tab.id as number, true);
+ }
+}
diff --git a/src/background/operators/impls/ReloadTabOperator.ts b/src/background/operators/impls/ReloadTabOperator.ts
new file mode 100644
index 0000000..db3389e
--- /dev/null
+++ b/src/background/operators/impls/ReloadTabOperator.ts
@@ -0,0 +1,14 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+
+export default class ReloadTabOperator implements Operator {
+ constructor(
+ private readonly tabPresenter: TabPresenter,
+ private readonly cache: boolean
+ ) {}
+
+ async run(): Promise<void> {
+ const tab = await this.tabPresenter.getCurrent();
+ return this.tabPresenter.reload(tab.id as number, this.cache);
+ }
+}
diff --git a/src/background/operators/impls/ReopenTabOperator.ts b/src/background/operators/impls/ReopenTabOperator.ts
new file mode 100644
index 0000000..507b4a8
--- /dev/null
+++ b/src/background/operators/impls/ReopenTabOperator.ts
@@ -0,0 +1,10 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+
+export default class ReopenTabOperator implements Operator {
+ constructor(private readonly tabPresenter: TabPresenter) {}
+
+ run(): Promise<void> {
+ return this.tabPresenter.reopen();
+ }
+}
diff --git a/src/background/operators/impls/RepeatLastOperator.ts b/src/background/operators/impls/RepeatLastOperator.ts
new file mode 100644
index 0000000..d46daab
--- /dev/null
+++ b/src/background/operators/impls/RepeatLastOperator.ts
@@ -0,0 +1,18 @@
+import Operator from "../Operator";
+import RepeatRepository from "../../repositories/RepeatRepository";
+import OperatorFactory from "../OperatorFactory";
+
+export default class RepeatLastOperator implements Operator {
+ constructor(
+ private readonly repeatRepository: RepeatRepository,
+ private readonly operatorFactory: OperatorFactory
+ ) {}
+
+ run(): Promise<void> {
+ const op = this.repeatRepository.getLastOperation();
+ if (typeof op === "undefined") {
+ return Promise.resolve();
+ }
+ return this.operatorFactory.create(op).run();
+ }
+}
diff --git a/src/background/operators/impls/RepeatOperatorFactoryChain.ts b/src/background/operators/impls/RepeatOperatorFactoryChain.ts
new file mode 100644
index 0000000..5038d48
--- /dev/null
+++ b/src/background/operators/impls/RepeatOperatorFactoryChain.ts
@@ -0,0 +1,29 @@
+import { inject, injectable } from "tsyringe";
+import Operator from "../Operator";
+import OperatorFactoryChain from "../OperatorFactoryChain";
+import RepeatLastOperator from "./RepeatLastOperator";
+import RepeatRepository from "../../repositories/RepeatRepository";
+import OperatorFactory from "../OperatorFactory";
+import * as operations from "../../../shared/operations";
+
+@injectable()
+export default class RepeatOperatorFactoryChain
+ implements OperatorFactoryChain {
+ constructor(
+ @inject("RepeatRepository")
+ private readonly repeatRepository: RepeatRepository,
+ @inject("OperatorFactory")
+ private readonly operatorFactory: OperatorFactory
+ ) {}
+
+ create(op: operations.Operation): Operator | null {
+ switch (op.type) {
+ case operations.REPEAT_LAST:
+ return new RepeatLastOperator(
+ this.repeatRepository,
+ this.operatorFactory
+ );
+ }
+ return null;
+ }
+}
diff --git a/src/background/operators/impls/ResetZoomOperator.ts b/src/background/operators/impls/ResetZoomOperator.ts
new file mode 100644
index 0000000..96af733
--- /dev/null
+++ b/src/background/operators/impls/ResetZoomOperator.ts
@@ -0,0 +1,10 @@
+import Operator from "../Operator";
+import ZoomPresenter from "../../presenters/ZoomPresenter";
+
+export default class ResetZoomOperator implements Operator {
+ constructor(private readonly zoomPresenter: ZoomPresenter) {}
+
+ run(): Promise<void> {
+ return this.zoomPresenter.resetZoom();
+ }
+}
diff --git a/src/background/operators/impls/SelectFirstTabOperator.ts b/src/background/operators/impls/SelectFirstTabOperator.ts
new file mode 100644
index 0000000..c04b8a9
--- /dev/null
+++ b/src/background/operators/impls/SelectFirstTabOperator.ts
@@ -0,0 +1,11 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+
+export default class SelectFirstTabOperator implements Operator {
+ constructor(private readonly tabPresenter: TabPresenter) {}
+
+ async run(): Promise<void> {
+ const tabs = await this.tabPresenter.getAll();
+ return this.tabPresenter.select(tabs[0].id as number);
+ }
+}
diff --git a/src/background/operators/impls/SelectLastTabOperator.ts b/src/background/operators/impls/SelectLastTabOperator.ts
new file mode 100644
index 0000000..e16d406
--- /dev/null
+++ b/src/background/operators/impls/SelectLastTabOperator.ts
@@ -0,0 +1,11 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+
+export default class SelectLastTabOperator implements Operator {
+ constructor(private readonly tabPresenter: TabPresenter) {}
+
+ async run(): Promise<void> {
+ const tabs = await this.tabPresenter.getAll();
+ return this.tabPresenter.select(tabs[tabs.length - 1].id as number);
+ }
+}
diff --git a/src/background/operators/impls/SelectPreviousSelectedTabOperator.ts b/src/background/operators/impls/SelectPreviousSelectedTabOperator.ts
new file mode 100644
index 0000000..03a778d
--- /dev/null
+++ b/src/background/operators/impls/SelectPreviousSelectedTabOperator.ts
@@ -0,0 +1,14 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+
+export default class SelectPreviousSelectedTabOperator implements Operator {
+ constructor(private readonly tabPresenter: TabPresenter) {}
+
+ async run(): Promise<void> {
+ const tabId = await this.tabPresenter.getLastSelectedId();
+ if (tabId === null || typeof tabId === "undefined") {
+ return Promise.resolve();
+ }
+ return this.tabPresenter.select(tabId);
+ }
+}
diff --git a/src/background/operators/impls/SelectTabNextOperator.ts b/src/background/operators/impls/SelectTabNextOperator.ts
new file mode 100644
index 0000000..34d37ff
--- /dev/null
+++ b/src/background/operators/impls/SelectTabNextOperator.ts
@@ -0,0 +1,19 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+
+export default class SelectTabNextOperator implements Operator {
+ constructor(private readonly tabPresenter: TabPresenter) {}
+
+ async run(): Promise<void> {
+ const tabs = await this.tabPresenter.getAll();
+ if (tabs.length < 2) {
+ return;
+ }
+ const tab = tabs.find((t) => t.active);
+ if (!tab) {
+ return;
+ }
+ const select = (tab.index + 1) % tabs.length;
+ return this.tabPresenter.select(tabs[select].id as number);
+ }
+}
diff --git a/src/background/operators/impls/SelectTabPrevOperator.ts b/src/background/operators/impls/SelectTabPrevOperator.ts
new file mode 100644
index 0000000..3776c1a
--- /dev/null
+++ b/src/background/operators/impls/SelectTabPrevOperator.ts
@@ -0,0 +1,19 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+
+export default class SelectTabPrevOperator implements Operator {
+ constructor(private readonly tabPresenter: TabPresenter) {}
+
+ async run(): Promise<void> {
+ const tabs = await this.tabPresenter.getAll();
+ if (tabs.length < 2) {
+ return;
+ }
+ const tab = tabs.find((t) => t.active);
+ if (!tab) {
+ return;
+ }
+ const select = (tab.index - 1 + tabs.length) % tabs.length;
+ return this.tabPresenter.select(tabs[select].id as number);
+ }
+}
diff --git a/src/background/operators/impls/ShowAddBookmarkOperator.ts b/src/background/operators/impls/ShowAddBookmarkOperator.ts
new file mode 100644
index 0000000..cce4879
--- /dev/null
+++ b/src/background/operators/impls/ShowAddBookmarkOperator.ts
@@ -0,0 +1,20 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+import ConsoleClient from "../../infrastructures/ConsoleClient";
+
+export default class ShowAddBookmarkOperator implements Operator {
+ constructor(
+ private readonly tabPresenter: TabPresenter,
+ private readonly consoleClient: ConsoleClient,
+ private readonly alter: boolean
+ ) {}
+
+ async run(): Promise<void> {
+ const tab = await this.tabPresenter.getCurrent();
+ let command = "addbookmark ";
+ if (this.alter) {
+ command += tab.title || "";
+ }
+ return this.consoleClient.showCommand(tab.id as number, command);
+ }
+}
diff --git a/src/background/operators/impls/ShowBufferCommandOperator.ts b/src/background/operators/impls/ShowBufferCommandOperator.ts
new file mode 100644
index 0000000..e1246e4
--- /dev/null
+++ b/src/background/operators/impls/ShowBufferCommandOperator.ts
@@ -0,0 +1,16 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+import ConsoleClient from "../../infrastructures/ConsoleClient";
+
+export default class ShowBufferCommandOperator implements Operator {
+ constructor(
+ private readonly tabPresenter: TabPresenter,
+ private readonly consoleClient: ConsoleClient
+ ) {}
+
+ async run(): Promise<void> {
+ const tab = await this.tabPresenter.getCurrent();
+ const command = "buffer ";
+ return this.consoleClient.showCommand(tab.id as number, command);
+ }
+}
diff --git a/src/background/operators/impls/ShowCommandOperator.ts b/src/background/operators/impls/ShowCommandOperator.ts
new file mode 100644
index 0000000..a5e1765
--- /dev/null
+++ b/src/background/operators/impls/ShowCommandOperator.ts
@@ -0,0 +1,15 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+import ConsoleClient from "../../infrastructures/ConsoleClient";
+
+export default class ShowCommandOperator implements Operator {
+ constructor(
+ private readonly tabPresenter: TabPresenter,
+ private readonly consoleClient: ConsoleClient
+ ) {}
+
+ async run(): Promise<void> {
+ const tab = await this.tabPresenter.getCurrent();
+ return this.consoleClient.showCommand(tab.id as number, "");
+ }
+}
diff --git a/src/background/operators/impls/ShowOpenCommandOperator.ts b/src/background/operators/impls/ShowOpenCommandOperator.ts
new file mode 100644
index 0000000..1e78e5f
--- /dev/null
+++ b/src/background/operators/impls/ShowOpenCommandOperator.ts
@@ -0,0 +1,20 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+import ConsoleClient from "../../infrastructures/ConsoleClient";
+
+export default class ShowOpenCommandOperator implements Operator {
+ constructor(
+ private readonly tabPresenter: TabPresenter,
+ private readonly consoleClient: ConsoleClient,
+ private readonly alter: boolean
+ ) {}
+
+ async run(): Promise<void> {
+ const tab = await this.tabPresenter.getCurrent();
+ let command = "open ";
+ if (this.alter) {
+ command += tab.url || "";
+ }
+ return this.consoleClient.showCommand(tab.id as number, command);
+ }
+}
diff --git a/src/background/operators/impls/ShowTabOpenCommandOperator.ts b/src/background/operators/impls/ShowTabOpenCommandOperator.ts
new file mode 100644
index 0000000..d734da1
--- /dev/null
+++ b/src/background/operators/impls/ShowTabOpenCommandOperator.ts
@@ -0,0 +1,20 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+import ConsoleClient from "../../infrastructures/ConsoleClient";
+
+export default class ShowTabOpenCommandOperator implements Operator {
+ constructor(
+ private readonly tabPresenter: TabPresenter,
+ private readonly consoleClient: ConsoleClient,
+ private readonly alter: boolean
+ ) {}
+
+ async run(): Promise<void> {
+ const tab = await this.tabPresenter.getCurrent();
+ let command = "tabopen ";
+ if (this.alter) {
+ command += tab.url || "";
+ }
+ return this.consoleClient.showCommand(tab.id as number, command);
+ }
+}
diff --git a/src/background/operators/impls/ShowWinOpenCommandOperator.ts b/src/background/operators/impls/ShowWinOpenCommandOperator.ts
new file mode 100644
index 0000000..3c5e639
--- /dev/null
+++ b/src/background/operators/impls/ShowWinOpenCommandOperator.ts
@@ -0,0 +1,20 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+import ConsoleClient from "../../infrastructures/ConsoleClient";
+
+export default class ShowWinOpenCommandOperator implements Operator {
+ constructor(
+ private readonly tabPresenter: TabPresenter,
+ private readonly consoleClient: ConsoleClient,
+ private readonly alter: boolean
+ ) {}
+
+ async run(): Promise<void> {
+ const tab = await this.tabPresenter.getCurrent();
+ let command = "winopen ";
+ if (this.alter) {
+ command += tab.url || "";
+ }
+ return this.consoleClient.showCommand(tab.id as number, command);
+ }
+}
diff --git a/src/background/operators/impls/StartFindOperator.ts b/src/background/operators/impls/StartFindOperator.ts
new file mode 100644
index 0000000..284ac9b
--- /dev/null
+++ b/src/background/operators/impls/StartFindOperator.ts
@@ -0,0 +1,15 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+import ConsoleClient from "../../infrastructures/ConsoleClient";
+
+export default class StartFindOperator implements Operator {
+ constructor(
+ private readonly tabPresenter: TabPresenter,
+ private readonly consoleClient: ConsoleClient
+ ) {}
+
+ async run(): Promise<void> {
+ const tab = await this.tabPresenter.getCurrent();
+ return this.consoleClient.showFind(tab.id as number);
+ }
+}
diff --git a/src/background/operators/impls/TabOperatorFactoryChain.ts b/src/background/operators/impls/TabOperatorFactoryChain.ts
new file mode 100644
index 0000000..edd3eaf
--- /dev/null
+++ b/src/background/operators/impls/TabOperatorFactoryChain.ts
@@ -0,0 +1,64 @@
+import { inject, injectable } from "tsyringe";
+import OperatorFactoryChain from "../OperatorFactoryChain";
+import * as operations from "../../../shared/operations";
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+import CloseTabOperator from "./CloseTabOperator";
+import CloseTabRightOperator from "./CloseTabRightOperator";
+import ReopenTabOperator from "./ReopenTabOperator";
+import SelectTabPrevOperator from "./SelectTabPrevOperator";
+import SelectTabNextOperator from "./SelectTabNextOperator";
+import SelectFirstTabOperator from "./SelectFirstTabOperator";
+import SelectLastTabOperator from "./SelectLastTabOperator";
+import SelectPreviousSelectedTabOperator from "./SelectPreviousSelectedTabOperator";
+import ReloadTabOperator from "./ReloadTabOperator";
+import PinTabOperator from "./PinTabOperator";
+import UnpinTabOperator from "./UnpinTabOperator";
+import TogglePinnedTabOperator from "./TogglePinnedTabOperator";
+import DuplicateTabOperator from "./DuplicateTabOperator";
+
+@injectable()
+export default class TabOperatorFactoryChain implements OperatorFactoryChain {
+ constructor(
+ @inject("TabPresenter")
+ private readonly tabPresenter: TabPresenter
+ ) {}
+
+ create(op: operations.Operation): Operator | null {
+ switch (op.type) {
+ case operations.TAB_CLOSE:
+ return new CloseTabOperator(
+ this.tabPresenter,
+ false,
+ op.select === "left"
+ );
+ case operations.TAB_CLOSE_RIGHT:
+ return new CloseTabRightOperator(this.tabPresenter);
+ case operations.TAB_CLOSE_FORCE:
+ return new CloseTabOperator(this.tabPresenter, true, false);
+ case operations.TAB_REOPEN:
+ return new ReopenTabOperator(this.tabPresenter);
+ case operations.TAB_PREV:
+ return new SelectTabPrevOperator(this.tabPresenter);
+ case operations.TAB_NEXT:
+ return new SelectTabNextOperator(this.tabPresenter);
+ case operations.TAB_FIRST:
+ return new SelectFirstTabOperator(this.tabPresenter);
+ case operations.TAB_LAST:
+ return new SelectLastTabOperator(this.tabPresenter);
+ case operations.TAB_PREV_SEL:
+ return new SelectPreviousSelectedTabOperator(this.tabPresenter);
+ case operations.TAB_RELOAD:
+ return new ReloadTabOperator(this.tabPresenter, op.cache);
+ case operations.TAB_PIN:
+ return new PinTabOperator(this.tabPresenter);
+ case operations.TAB_UNPIN:
+ return new UnpinTabOperator(this.tabPresenter);
+ case operations.TAB_TOGGLE_PINNED:
+ return new TogglePinnedTabOperator(this.tabPresenter);
+ case operations.TAB_DUPLICATE:
+ return new DuplicateTabOperator(this.tabPresenter);
+ }
+ return null;
+ }
+}
diff --git a/src/background/operators/impls/TogglePinnedTabOperator.ts b/src/background/operators/impls/TogglePinnedTabOperator.ts
new file mode 100644
index 0000000..c4fd4ba
--- /dev/null
+++ b/src/background/operators/impls/TogglePinnedTabOperator.ts
@@ -0,0 +1,11 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+
+export default class TogglePinnedTabOperator implements Operator {
+ constructor(private readonly tabPresenter: TabPresenter) {}
+
+ async run(): Promise<void> {
+ const tab = await this.tabPresenter.getCurrent();
+ return this.tabPresenter.setPinned(tab.id as number, !tab.pinned);
+ }
+}
diff --git a/src/background/operators/impls/UnpinTabOperator.ts b/src/background/operators/impls/UnpinTabOperator.ts
new file mode 100644
index 0000000..fa50145
--- /dev/null
+++ b/src/background/operators/impls/UnpinTabOperator.ts
@@ -0,0 +1,11 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+
+export default class UnpinTabOperator implements Operator {
+ constructor(private readonly tabPresenter: TabPresenter) {}
+
+ async run(): Promise<void> {
+ const tab = await this.tabPresenter.getCurrent();
+ return this.tabPresenter.setPinned(tab.id as number, false);
+ }
+}
diff --git a/src/background/operators/impls/ZoomInOperator.ts b/src/background/operators/impls/ZoomInOperator.ts
new file mode 100644
index 0000000..e900f0d
--- /dev/null
+++ b/src/background/operators/impls/ZoomInOperator.ts
@@ -0,0 +1,10 @@
+import Operator from "../Operator";
+import ZoomPresenter from "../../presenters/ZoomPresenter";
+
+export default class ZoomInOperator implements Operator {
+ constructor(private readonly zoomPresenter: ZoomPresenter) {}
+
+ run(): Promise<void> {
+ return this.zoomPresenter.zoomIn();
+ }
+}
diff --git a/src/background/operators/impls/ZoomOperatorFactoryChain.ts b/src/background/operators/impls/ZoomOperatorFactoryChain.ts
new file mode 100644
index 0000000..bf930a7
--- /dev/null
+++ b/src/background/operators/impls/ZoomOperatorFactoryChain.ts
@@ -0,0 +1,28 @@
+import { inject, injectable } from "tsyringe";
+import Operator from "../Operator";
+import OperatorFactoryChain from "../OperatorFactoryChain";
+import ZoomInOperator from "./ZoomInOperator";
+import ZoomOutOperator from "./ZoomOutOperator";
+import ResetZoomOperator from "./ResetZoomOperator";
+import ZoomPresenter from "../../presenters/ZoomPresenter";
+import * as operations from "../../../shared/operations";
+
+@injectable()
+export default class ZoomOperatorFactoryChain implements OperatorFactoryChain {
+ constructor(
+ @inject("ZoomPresenter")
+ private readonly zoomPresenter: ZoomPresenter
+ ) {}
+
+ create(op: operations.Operation): Operator | null {
+ switch (op.type) {
+ case operations.ZOOM_IN:
+ return new ZoomInOperator(this.zoomPresenter);
+ case operations.ZOOM_OUT:
+ return new ZoomOutOperator(this.zoomPresenter);
+ case operations.ZOOM_NEUTRAL:
+ return new ResetZoomOperator(this.zoomPresenter);
+ }
+ return null;
+ }
+}
diff --git a/src/background/operators/impls/ZoomOutOperator.ts b/src/background/operators/impls/ZoomOutOperator.ts
new file mode 100644
index 0000000..0c0389e
--- /dev/null
+++ b/src/background/operators/impls/ZoomOutOperator.ts
@@ -0,0 +1,10 @@
+import Operator from "../Operator";
+import ZoomPresenter from "../../presenters/ZoomPresenter";
+
+export default class ZoomOutOperator implements Operator {
+ constructor(private readonly zoomPresenter: ZoomPresenter) {}
+
+ run(): Promise<void> {
+ return this.zoomPresenter.zoomOut();
+ }
+}
diff --git a/src/background/presenters/Notifier.ts b/src/background/presenters/Notifier.ts
index 2cd3225..957572d 100644
--- a/src/background/presenters/Notifier.ts
+++ b/src/background/presenters/Notifier.ts
@@ -30,10 +30,10 @@ export class NotifierImpl implements NotifierImpl {
}
async notifyInvalidSettings(onclick: () => void): Promise<void> {
- const title = `Loaded settings is invalid`;
+ const title = `Loading settings failed`;
// eslint-disable-next-line max-len
const message =
- "The default settings is used due to the last saved settings is invalid. Check your current settings from the add-on preference";
+ "The default settings are used due to the last saved settings is invalid. Check your current settings from the add-on preference";
const listener = (id: string) => {
if (id !== NOTIFICATION_ID_INVALID_SETTINGS) {
diff --git a/src/background/presenters/WindowPresenter.ts b/src/background/presenters/WindowPresenter.ts
index 4f37f5d..5eb0f22 100644
--- a/src/background/presenters/WindowPresenter.ts
+++ b/src/background/presenters/WindowPresenter.ts
@@ -1,8 +1,9 @@
-import { injectable } from "tsyringe";
+export default interface WindowPresenter {
+ create(url: string): Promise<void>;
+}
-@injectable()
-export default class WindowPresenter {
- create(url: string): Promise<browser.windows.Window> {
- return browser.windows.create({ url });
+export class WindowPresenterImpl implements WindowPresenter {
+ async create(url: string): Promise<void> {
+ await browser.windows.create({ url });
}
}
diff --git a/src/background/presenters/ZoomPresenter.ts b/src/background/presenters/ZoomPresenter.ts
new file mode 100644
index 0000000..5a3c64d
--- /dev/null
+++ b/src/background/presenters/ZoomPresenter.ts
@@ -0,0 +1,60 @@
+const ZOOM_SETTINGS = [
+ 0.33,
+ 0.5,
+ 0.66,
+ 0.75,
+ 0.8,
+ 0.9,
+ 1.0,
+ 1.1,
+ 1.25,
+ 1.5,
+ 1.75,
+ 2.0,
+ 2.5,
+ 3.0,
+] as const;
+
+export default interface ZoomPresenter {
+ zoomIn(): Promise<void>;
+ zoomOut(): Promise<void>;
+ resetZoom(): Promise<void>;
+}
+
+export class ZoomPresenterImpl implements ZoomPresenter {
+ async zoomIn(): Promise<void> {
+ const tab = await browser.tabs.query({
+ active: true,
+ currentWindow: true,
+ });
+ const tabId = tab[0].id as number;
+ const current = await browser.tabs.getZoom(tabId);
+ const factor = ZOOM_SETTINGS.find((f) => f > current);
+ if (factor) {
+ return browser.tabs.setZoom(tabId, factor);
+ }
+ }
+
+ async zoomOut(): Promise<void> {
+ const tab = await browser.tabs.query({
+ active: true,
+ currentWindow: true,
+ });
+ const tabId = tab[0].id as number;
+ const current = await browser.tabs.getZoom(tabId);
+ const factor = ZOOM_SETTINGS.slice(0)
+ .reverse()
+ .find((f) => f < current);
+ if (factor) {
+ return browser.tabs.setZoom(tabId, factor);
+ }
+ }
+
+ async resetZoom(): Promise<void> {
+ const tab = await browser.tabs.query({
+ active: true,
+ currentWindow: true,
+ });
+ return browser.tabs.setZoom(tab[0].id, 1);
+ }
+}
diff --git a/src/background/repositories/BrowserSettingRepository.ts b/src/background/repositories/BrowserSettingRepository.ts
index e24874b..1dde190 100644
--- a/src/background/repositories/BrowserSettingRepository.ts
+++ b/src/background/repositories/BrowserSettingRepository.ts
@@ -1,8 +1,12 @@
import { injectable } from "tsyringe";
import * as urls from "../../shared/urls";
+export default interface BrowserSettingRepository {
+ getHomepageUrls(): Promise<string[]>;
+}
+
@injectable()
-export default class BrowserSettingRepository {
+export class BrowserSettingRepositoryImpl implements BrowserSettingRepository {
async getHomepageUrls(): Promise<string[]> {
const { value } = await browser.browserSettings.homepageOverride.get({});
return value.split("|").map(urls.normalizeUrl);
diff --git a/src/background/repositories/RepeatRepository.ts b/src/background/repositories/RepeatRepository.ts
index e3ab43d..00098d3 100644
--- a/src/background/repositories/RepeatRepository.ts
+++ b/src/background/repositories/RepeatRepository.ts
@@ -4,8 +4,14 @@ import MemoryStorage from "../infrastructures/MemoryStorage";
const REPEAT_KEY = "repeat";
+export default interface RepeatRepository {
+ getLastOperation(): Operation | undefined;
+
+ setLastOperation(op: Operation): void;
+}
+
@injectable()
-export default class RepeatRepository {
+export class RepeatRepositoryImpl implements RepeatRepository {
private cache: MemoryStorage;
constructor() {
diff --git a/src/background/usecases/AddonEnabledUseCase.ts b/src/background/usecases/AddonEnabledUseCase.ts
index f563ab0..f9bafde 100644
--- a/src/background/usecases/AddonEnabledUseCase.ts
+++ b/src/background/usecases/AddonEnabledUseCase.ts
@@ -22,7 +22,7 @@ export default class AddonEnabledUseCase {
return this.indicatorPresentor.indicate(enabled);
}
- onIndicatorClick(tabId: number): Promise<void> {
+ private onIndicatorClick(tabId: number): Promise<void> {
return this.contentMessageClient.toggleAddonEnabled(tabId);
}
diff --git a/src/background/usecases/CommandUseCase.ts b/src/background/usecases/CommandUseCase.ts
index 811ec77..18ddd4d 100644
--- a/src/background/usecases/CommandUseCase.ts
+++ b/src/background/usecases/CommandUseCase.ts
@@ -12,17 +12,20 @@ import ContentMessageClient from "../infrastructures/ContentMessageClient";
import RepeatUseCase from "../usecases/RepeatUseCase";
@injectable()
-export default class CommandIndicator {
+export default class CommandUseCase {
constructor(
- @inject("TabPresenter") private tabPresenter: TabPresenter,
- private windowPresenter: WindowPresenter,
- private helpPresenter: HelpPresenter,
+ @inject("TabPresenter")
+ private readonly tabPresenter: TabPresenter,
+ @inject("WindowPresenter")
+ private readonly windowPresenter: WindowPresenter,
+ private readonly helpPresenter: HelpPresenter,
@inject("CachedSettingRepository")
- private cachedSettingRepository: CachedSettingRepository,
- private bookmarkRepository: BookmarkRepository,
- private consoleClient: ConsoleClient,
- private contentMessageClient: ContentMessageClient,
- private repeatUseCase: RepeatUseCase
+ private readonly cachedSettingRepository: CachedSettingRepository,
+ private readonly bookmarkRepository: BookmarkRepository,
+ @inject("ConsoleClient")
+ private readonly consoleClient: ConsoleClient,
+ private readonly contentMessageClient: ContentMessageClient,
+ private readonly repeatUseCase: RepeatUseCase
) {}
async open(keywords: string): Promise<browser.tabs.Tab> {
@@ -44,7 +47,7 @@ export default class CommandIndicator {
return this.tabPresenter.create(url);
}
- async winopen(keywords: string): Promise<browser.windows.Window> {
+ async winopen(keywords: string): Promise<void> {
const url = await this.urlOrSearch(keywords);
this.repeatUseCase.storeLastOperation({
type: operations.INTERNAL_OPEN_URL,
diff --git a/src/background/usecases/ConsoleUseCase.ts b/src/background/usecases/ConsoleUseCase.ts
deleted file mode 100644
index 195c70f..0000000
--- a/src/background/usecases/ConsoleUseCase.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import { inject, injectable } from "tsyringe";
-import TabPresenter from "../presenters/TabPresenter";
-import ConsoleClient from "../infrastructures/ConsoleClient";
-
-@injectable()
-export default class ConsoleUseCase {
- constructor(
- @inject("TabPresenter") private tabPresenter: TabPresenter,
- private consoleClient: ConsoleClient
- ) {}
-
- async showCommand(): Promise<any> {
- const tab = await this.tabPresenter.getCurrent();
- return this.consoleClient.showCommand(tab.id as number, "");
- }
-
- async showOpenCommand(alter: boolean): Promise<any> {
- const tab = await this.tabPresenter.getCurrent();
- let command = "open ";
- if (alter) {
- command += tab.url || "";
- }
- return this.consoleClient.showCommand(tab.id as number, command);
- }
-
- async showTabopenCommand(alter: boolean): Promise<any> {
- const tab = await this.tabPresenter.getCurrent();
- let command = "tabopen ";
- if (alter) {
- command += tab.url || "";
- }
- return this.consoleClient.showCommand(tab.id as number, command);
- }
-
- async showWinopenCommand(alter: boolean): Promise<any> {
- const tab = await this.tabPresenter.getCurrent();
- let command = "winopen ";
- if (alter) {
- command += tab.url || "";
- }
- return this.consoleClient.showCommand(tab.id as number, command);
- }
-
- async showBufferCommand(): Promise<any> {
- const tab = await this.tabPresenter.getCurrent();
- const command = "buffer ";
- return this.consoleClient.showCommand(tab.id as number, command);
- }
-
- async showAddbookmarkCommand(alter: boolean): Promise<any> {
- const tab = await this.tabPresenter.getCurrent();
- let command = "addbookmark ";
- if (alter) {
- command += tab.title || "";
- }
- return this.consoleClient.showCommand(tab.id as number, command);
- }
-
- async hideConsole(): Promise<any> {
- const tab = await this.tabPresenter.getCurrent();
- return this.consoleClient.hide(tab.id as number);
- }
-}
diff --git a/src/background/usecases/FindUseCase.ts b/src/background/usecases/FindUseCase.ts
index facc461..ce96e03 100644
--- a/src/background/usecases/FindUseCase.ts
+++ b/src/background/usecases/FindUseCase.ts
@@ -1,15 +1,9 @@
-import { inject, injectable } from "tsyringe";
+import { injectable } from "tsyringe";
import FindRepository from "../repositories/FindRepository";
-import TabPresenter from "../presenters/TabPresenter";
-import ConsoleClient from "../infrastructures/ConsoleClient";
@injectable()
export default class FindUseCase {
- constructor(
- @inject("TabPresenter") private tabPresenter: TabPresenter,
- private findRepository: FindRepository,
- private consoleClient: ConsoleClient
- ) {}
+ constructor(private readonly findRepository: FindRepository) {}
getKeyword(): Promise<string> {
return this.findRepository.getKeyword();
@@ -18,9 +12,4 @@ export default class FindUseCase {
setKeyword(keyword: string): Promise<any> {
return this.findRepository.setKeyword(keyword);
}
-
- async findStart(): Promise<any> {
- const tab = await this.tabPresenter.getCurrent();
- return this.consoleClient.showFind(tab.id as number);
- }
}
diff --git a/src/background/usecases/MarkUseCase.ts b/src/background/usecases/MarkUseCase.ts
index 9da9a21..57e04b4 100644
--- a/src/background/usecases/MarkUseCase.ts
+++ b/src/background/usecases/MarkUseCase.ts
@@ -1,16 +1,18 @@
import { inject, injectable } from "tsyringe";
import TabPresenter from "../presenters/TabPresenter";
import MarkRepository from "../repositories/MarkRepository";
-import ConsoleClient from "../infrastructures/ConsoleClient";
import ContentMessageClient from "../infrastructures/ContentMessageClient";
+import ConsoleClient from "../infrastructures/ConsoleClient";
@injectable()
export default class MarkUseCase {
constructor(
- @inject("TabPresenter") private tabPresenter: TabPresenter,
- private markRepository: MarkRepository,
- private consoleClient: ConsoleClient,
- private contentMessageClient: ContentMessageClient
+ @inject("TabPresenter")
+ private readonly tabPresenter: TabPresenter,
+ private readonly markRepository: MarkRepository,
+ @inject("ConsoleClient")
+ private readonly consoleClient: ConsoleClient,
+ private readonly contentMessageClient: ContentMessageClient
) {}
async setGlobal(key: string, x: number, y: number): Promise<any> {
diff --git a/src/background/usecases/NavigateUseCase.ts b/src/background/usecases/NavigateUseCase.ts
deleted file mode 100644
index 974606c..0000000
--- a/src/background/usecases/NavigateUseCase.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import { inject, injectable } from "tsyringe";
-import NavigateClient from "../clients/NavigateClient";
-import TabPresenter from "../presenters/TabPresenter";
-
-@injectable()
-export default class NavigateUseCase {
- constructor(
- @inject("TabPresenter") private tabPresenter: TabPresenter,
- private navigateClient: NavigateClient
- ) {}
-
- async openHistoryNext(): Promise<void> {
- const tab = await this.tabPresenter.getCurrent();
- await this.navigateClient.historyNext(tab.id!);
- }
-
- async openHistoryPrev(): Promise<void> {
- const tab = await this.tabPresenter.getCurrent();
- await this.navigateClient.historyPrev(tab.id!);
- }
-
- async openLinkNext(): Promise<void> {
- const tab = await this.tabPresenter.getCurrent();
- await this.navigateClient.linkNext(tab.id!);
- }
-
- async openLinkPrev(): Promise<void> {
- const tab = await this.tabPresenter.getCurrent();
- await this.navigateClient.linkPrev(tab.id!);
- }
-
- async openParent(): Promise<void> {
- const tab = await this.tabPresenter.getCurrent();
- const url = new URL(tab.url!);
- if (url.hash.length > 0) {
- url.hash = "";
- } else if (url.search.length > 0) {
- url.search = "";
- } else {
- const basenamePattern = /\/[^/]+$/;
- const lastDirPattern = /\/[^/]+\/$/;
- if (basenamePattern.test(url.pathname)) {
- url.pathname = url.pathname.replace(basenamePattern, "/");
- } else if (lastDirPattern.test(url.pathname)) {
- url.pathname = url.pathname.replace(lastDirPattern, "/");
- }
- }
- await this.tabPresenter.open(url.href);
- }
-
- async openRoot(): Promise<void> {
- const tab = await this.tabPresenter.getCurrent();
- const url = new URL(tab.url!);
- await this.tabPresenter.open(url.origin);
- }
-}
diff --git a/src/background/usecases/RepeatUseCase.ts b/src/background/usecases/RepeatUseCase.ts
index d7235ee..992e76b 100644
--- a/src/background/usecases/RepeatUseCase.ts
+++ b/src/background/usecases/RepeatUseCase.ts
@@ -1,4 +1,4 @@
-import { injectable } from "tsyringe";
+import { inject, injectable } from "tsyringe";
import * as operations from "../../shared/operations";
import RepeatRepository from "../repositories/RepeatRepository";
@@ -6,7 +6,10 @@ type Operation = operations.Operation;
@injectable()
export default class RepeatUseCase {
- constructor(private repeatRepository: RepeatRepository) {}
+ constructor(
+ @inject("RepeatRepository")
+ private readonly repeatRepository: RepeatRepository
+ ) {}
storeLastOperation(op: Operation): void {
this.repeatRepository.setLastOperation(op);
diff --git a/src/background/usecases/TabSelectUseCase.ts b/src/background/usecases/TabSelectUseCase.ts
deleted file mode 100644
index 663ceb8..0000000
--- a/src/background/usecases/TabSelectUseCase.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { inject, injectable } from "tsyringe";
-import TabPresenter from "../presenters/TabPresenter";
-
-@injectable()
-export default class TabSelectUseCase {
- constructor(@inject("TabPresenter") private tabPresenter: TabPresenter) {}
-
- async selectPrev(count: number): Promise<any> {
- const tabs = await this.tabPresenter.getAll();
- if (tabs.length < 2) {
- return;
- }
- const tab = tabs.find((t) => t.active);
- if (!tab) {
- return;
- }
- const select = (tab.index - count + tabs.length) % tabs.length;
- return this.tabPresenter.select(tabs[select].id as number);
- }
-
- async selectNext(count: number): Promise<any> {
- const tabs = await this.tabPresenter.getAll();
- if (tabs.length < 2) {
- return;
- }
- const tab = tabs.find((t) => t.active);
- if (!tab) {
- return;
- }
- const select = (tab.index + count) % tabs.length;
- return this.tabPresenter.select(tabs[select].id as number);
- }
-
- async selectFirst(): Promise<any> {
- const tabs = await this.tabPresenter.getAll();
- return this.tabPresenter.select(tabs[0].id as number);
- }
-
- async selectLast(): Promise<any> {
- const tabs = await this.tabPresenter.getAll();
- return this.tabPresenter.select(tabs[tabs.length - 1].id as number);
- }
-
- async selectPrevSelected(): Promise<any> {
- const tabId = await this.tabPresenter.getLastSelectedId();
- if (tabId === null || typeof tabId === "undefined") {
- return Promise.resolve();
- }
- return this.tabPresenter.select(tabId);
- }
-}
diff --git a/src/background/usecases/TabUseCase.ts b/src/background/usecases/TabUseCase.ts
deleted file mode 100644
index 1439107..0000000
--- a/src/background/usecases/TabUseCase.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-import { inject, injectable } from "tsyringe";
-import TabPresenter from "../presenters/TabPresenter";
-import WindowPresenter from "../presenters/WindowPresenter";
-import BrowserSettingRepository from "../repositories/BrowserSettingRepository";
-
-@injectable()
-export default class TabUseCase {
- constructor(
- @inject("TabPresenter") private tabPresenter: TabPresenter,
- private windowPresenter: WindowPresenter,
- private browserSettingRepository: BrowserSettingRepository
- ) {}
-
- async close(force: boolean, selectLeft = false): Promise<any> {
- const tab = await this.tabPresenter.getCurrent();
- if (!force && tab.pinned) {
- return Promise.resolve();
- }
- if (selectLeft && tab.index > 0) {
- const tabs = await this.tabPresenter.getAll();
- await this.tabPresenter.select(tabs[tab.index - 1].id as number);
- }
- return this.tabPresenter.remove([tab.id as number]);
- }
-
- async closeRight(): Promise<any> {
- const tabs = await this.tabPresenter.getAll();
- tabs.sort((t1, t2) => t1.index - t2.index);
- const index = tabs.findIndex((t) => t.active);
- if (index < 0) {
- return;
- }
- for (let i = index + 1; i < tabs.length; ++i) {
- const tab = tabs[i];
- if (!tab.pinned) {
- this.tabPresenter.remove([tab.id as number]);
- }
- }
- }
-
- reopen(): Promise<any> {
- return this.tabPresenter.reopen();
- }
-
- async reload(cache: boolean): Promise<any> {
- const tab = await this.tabPresenter.getCurrent();
- return this.tabPresenter.reload(tab.id as number, cache);
- }
-
- async setPinned(pinned: boolean): Promise<any> {
- const tab = await this.tabPresenter.getCurrent();
- return this.tabPresenter.setPinned(tab.id as number, pinned);
- }
-
- async togglePinned(): Promise<any> {
- const tab = await this.tabPresenter.getCurrent();
- return this.tabPresenter.setPinned(tab.id as number, !tab.pinned);
- }
-
- async duplicate(): Promise<any> {
- const tab = await this.tabPresenter.getCurrent();
- return this.tabPresenter.duplicate(tab.id as number);
- }
-
- async openPageSource(): Promise<any> {
- const tab = await this.tabPresenter.getCurrent();
- const url = "view-source:" + tab.url;
- return this.tabPresenter.create(url);
- }
-
- async openHome(newTab: boolean): Promise<any> {
- const tab = await this.tabPresenter.getCurrent();
- const urls = await this.browserSettingRepository.getHomepageUrls();
- if (urls.length === 1 && urls[0] === "about:home") {
- // eslint-disable-next-line max-len
- throw new Error(
- "Cannot open Firefox Home (about:home) by WebExtensions, set your custom URLs"
- );
- }
- if (urls.length === 1 && !newTab) {
- return this.tabPresenter.open(urls[0], tab.id);
- }
- for (const url of urls) {
- this.tabPresenter.create(url);
- }
- }
-
- async openURL(
- url: string,
- newTab?: boolean,
- newWindow?: boolean
- ): Promise<void> {
- if (newWindow) {
- await this.windowPresenter.create(url);
- } else if (newTab) {
- await this.tabPresenter.create(url);
- } else {
- const tab = await this.tabPresenter.getCurrent();
- await this.tabPresenter.open(url, tab.id);
- }
- }
-}
diff --git a/src/background/usecases/ZoomUseCase.ts b/src/background/usecases/ZoomUseCase.ts
deleted file mode 100644
index 173e4d7..0000000
--- a/src/background/usecases/ZoomUseCase.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { inject, injectable } from "tsyringe";
-import TabPresenter from "../presenters/TabPresenter";
-
-const ZOOM_SETTINGS: number[] = [
- 0.33,
- 0.5,
- 0.66,
- 0.75,
- 0.8,
- 0.9,
- 1.0,
- 1.1,
- 1.25,
- 1.5,
- 1.75,
- 2.0,
- 2.5,
- 3.0,
-];
-
-@injectable()
-export default class ZoomUseCase {
- constructor(@inject("TabPresenter") private tabPresenter: TabPresenter) {}
-
- async zoomIn(): Promise<any> {
- const tab = await this.tabPresenter.getCurrent();
- const tabId = tab.id as number;
- const current = await this.tabPresenter.getZoom(tabId);
- const factor = ZOOM_SETTINGS.find((f) => f > current);
- if (factor) {
- return this.tabPresenter.setZoom(tabId as number, factor);
- }
- }
-
- async zoomOut(): Promise<any> {
- const tab = await this.tabPresenter.getCurrent();
- const tabId = tab.id as number;
- const current = await this.tabPresenter.getZoom(tabId);
- const factor = ZOOM_SETTINGS.slice(0)
- .reverse()
- .find((f) => f < current);
- if (factor) {
- return this.tabPresenter.setZoom(tabId as number, factor);
- }
- }
-
- async zoomNutoral(): Promise<any> {
- const tab = await this.tabPresenter.getCurrent();
- return this.tabPresenter.setZoom(tab.id as number, 1);
- }
-}
diff --git a/src/content/controllers/KeymapController.ts b/src/content/controllers/KeymapController.ts
index 092e55c..01ac8b5 100644
--- a/src/content/controllers/KeymapController.ts
+++ b/src/content/controllers/KeymapController.ts
@@ -1,32 +1,15 @@
import { injectable, inject } from "tsyringe";
-import * as operations from "../../shared/operations";
import KeymapUseCase from "../usecases/KeymapUseCase";
-import AddonEnabledUseCase from "../usecases/AddonEnabledUseCase";
-import FindSlaveUseCase from "../usecases/FindSlaveUseCase";
-import ScrollUseCase from "../usecases/ScrollUseCase";
-import FocusUseCase from "../usecases/FocusUseCase";
-import ClipboardUseCase from "../usecases/ClipboardUseCase";
-import OperationClient from "../client/OperationClient";
-import MarkKeyyUseCase from "../usecases/MarkKeyUseCase";
-import FollowMasterClient from "../client/FollowMasterClient";
import Key from "../../shared/settings/Key";
+import OperatorFactory from "../operators/OperatorFactory";
@injectable()
export default class KeymapController {
constructor(
private keymapUseCase: KeymapUseCase,
- private addonEnabledUseCase: AddonEnabledUseCase,
- private findSlaveUseCase: FindSlaveUseCase,
- private scrollUseCase: ScrollUseCase,
- private focusUseCase: FocusUseCase,
- private clipbaordUseCase: ClipboardUseCase,
- private markKeyUseCase: MarkKeyyUseCase,
- @inject("OperationClient")
- private operationClient: OperationClient,
-
- @inject("FollowMasterClient")
- private followMasterClient: FollowMasterClient
+ @inject("OperatorFactory")
+ private readonly operatorFactory: OperatorFactory
) {}
// eslint-disable-next-line complexity, max-lines-per-function
@@ -36,65 +19,14 @@ export default class KeymapController {
return false;
}
- if (!operations.isNRepeatable(nextOp.op.type)) {
- nextOp.repeat = 1;
- }
-
- const doFunc = ((op: operations.Operation) => {
- switch (op.type) {
- case operations.ADDON_ENABLE:
- return () => this.addonEnabledUseCase.enable();
- case operations.ADDON_DISABLE:
- return () => this.addonEnabledUseCase.disable();
- case operations.ADDON_TOGGLE_ENABLED:
- return () => this.addonEnabledUseCase.toggle();
- case operations.FIND_NEXT:
- return () => this.findSlaveUseCase.findNext();
- case operations.FIND_PREV:
- return () => this.findSlaveUseCase.findPrev();
- case operations.SCROLL_VERTICALLY:
- return () => this.scrollUseCase.scrollVertically(op.count);
- case operations.SCROLL_HORIZONALLY:
- return () => this.scrollUseCase.scrollHorizonally(op.count);
- case operations.SCROLL_PAGES:
- return () => this.scrollUseCase.scrollPages(op.count);
- case operations.SCROLL_TOP:
- return () => this.scrollUseCase.scrollToTop();
- case operations.SCROLL_BOTTOM:
- return () => this.scrollUseCase.scrollToBottom();
- case operations.SCROLL_HOME:
- return () => this.scrollUseCase.scrollToHome();
- case operations.SCROLL_END:
- return () => this.scrollUseCase.scrollToEnd();
- case operations.FOLLOW_START:
- return () =>
- this.followMasterClient.startFollow(op.newTab, op.background);
- case operations.MARK_SET_PREFIX:
- return () => this.markKeyUseCase.enableSetMode();
- case operations.MARK_JUMP_PREFIX:
- return () => this.markKeyUseCase.enableJumpMode();
- case operations.FOCUS_INPUT:
- return () => this.focusUseCase.focusFirstInput();
- case operations.URLS_YANK:
- return () => this.clipbaordUseCase.yankCurrentURL();
- case operations.URLS_PASTE:
- return () =>
- this.clipbaordUseCase.openOrSearch(op.newTab ? op.newTab : false);
- default:
- return null;
- }
- })(nextOp.op);
+ // Do not await asynchronous methods to return a boolean immidiately. The
+ // caller requires the synchronous response from the callback to identify
+ // to continue of abandon the event propagation.
+ this.operatorFactory
+ .create(nextOp.op, nextOp.repeat)
+ .run()
+ .catch(console.error);
- if (doFunc === null) {
- // Do not await asynchronous methods to return a boolean immidiately. The
- // caller requires the synchronous response from the callback to identify
- // to continue of abandon the event propagations.
- this.operationClient.execBackgroundOp(nextOp.repeat, nextOp.op);
- } else {
- for (let i = 0; i < nextOp.repeat; ++i) {
- doFunc();
- }
- }
return true;
}
diff --git a/src/content/di.ts b/src/content/di.ts
index cc10c78..e74d7ac 100644
--- a/src/content/di.ts
+++ b/src/content/di.ts
@@ -17,6 +17,7 @@ import { FollowMasterRepositoryImpl } from "./repositories/FollowMasterRepositor
import { FollowPresenterImpl } from "./presenters/FollowPresenter";
import { FollowSlaveClientFactoryImpl } from "./client/FollowSlaveClientFactory";
import { FollowSlaveRepositoryImpl } from "./repositories/FollowSlaveRepository";
+import { HintKeyRepositoryImpl } from "./repositories/HintKeyRepository";
import { KeymapRepositoryImpl } from "./repositories/KeymapRepository";
import { MarkClientImpl } from "./client/MarkClient";
import { MarkKeyRepositoryImpl } from "./repositories/MarkKeyRepository";
@@ -28,6 +29,8 @@ import { SettingClientImpl } from "./client/SettingClient";
import { SettingRepositoryImpl } from "./repositories/SettingRepository";
import { TabsClientImpl } from "./client/TabsClient";
import { container } from "tsyringe";
+import OperatorFactoryImpl from "./operators/impls/OperatorFactoryImpl";
+import { URLRepositoryImpl } from "./operators/impls/URLRepository";
container.register("FollowMasterClient", {
useValue: new FollowMasterClientImpl(window.top),
@@ -64,6 +67,9 @@ container.register("FollowSlaveClientFactory", {
container.register("FollowSlaveRepository", {
useClass: FollowSlaveRepositoryImpl,
});
+container.register("HintKeyRepository", {
+ useClass: HintKeyRepositoryImpl,
+});
container.register("KeymapRepository", { useClass: KeymapRepositoryImpl });
container.register("MarkClient", { useClass: MarkClientImpl });
container.register("MarkKeyRepository", { useClass: MarkKeyRepositoryImpl });
@@ -75,4 +81,6 @@ container.register("OperationClient", { useClass: OperationClientImpl });
container.register("ScrollPresenter", { useClass: ScrollPresenterImpl });
container.register("SettingClient", { useClass: SettingClientImpl });
container.register("SettingRepository", { useClass: SettingRepositoryImpl });
+container.register("URLRepository", { useClass: URLRepositoryImpl });
container.register("TabsClient", { useClass: TabsClientImpl });
+container.register("OperatorFactory", { useClass: OperatorFactoryImpl });
diff --git a/src/content/hint-key-producer.ts b/src/content/hint-key-producer.ts
deleted file mode 100644
index a5e2877..0000000
--- a/src/content/hint-key-producer.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-export default class HintKeyProducer {
- private charset: string;
-
- private counter: number[];
-
- constructor(charset: string) {
- if (charset.length === 0) {
- throw new TypeError("charset is empty");
- }
-
- this.charset = charset;
- this.counter = [];
- }
-
- produce(): string {
- this.increment();
-
- return this.counter.map((x) => this.charset[x]).join("");
- }
-
- private increment(): void {
- const max = this.charset.length - 1;
- if (this.counter.every((x) => x === max)) {
- this.counter = new Array(this.counter.length + 1).fill(0);
- return;
- }
-
- this.counter.reverse();
- const len = this.charset.length;
- let num = this.counter.reduce((x, y, index) => x + y * len ** index) + 1;
- for (let i = 0; i < this.counter.length; ++i) {
- this.counter[i] = num % len;
- num = ~~(num / len);
- }
- this.counter.reverse();
- }
-}
diff --git a/src/content/operators/Operator.ts b/src/content/operators/Operator.ts
new file mode 100644
index 0000000..3b1fe03
--- /dev/null
+++ b/src/content/operators/Operator.ts
@@ -0,0 +1,5 @@
+interface Operator {
+ run(): Promise<void>;
+}
+
+export default Operator;
diff --git a/src/content/operators/OperatorFactory.ts b/src/content/operators/OperatorFactory.ts
new file mode 100644
index 0000000..f45973b
--- /dev/null
+++ b/src/content/operators/OperatorFactory.ts
@@ -0,0 +1,6 @@
+import * as operations from "../../shared/operations";
+import Operator from "./Operator";
+
+export default interface OperatorFactory {
+ create(op: operations.Operation, repeat: number): Operator;
+}
diff --git a/src/content/operators/OperatorFactoryChain.ts b/src/content/operators/OperatorFactoryChain.ts
new file mode 100644
index 0000000..98dedb5
--- /dev/null
+++ b/src/content/operators/OperatorFactoryChain.ts
@@ -0,0 +1,6 @@
+import * as operations from "../../shared/operations";
+import Operator from "./Operator";
+
+export default interface OperatorFactoryChain {
+ create(op: operations.Operation, repeat: number): Operator | null;
+}
diff --git a/src/content/operators/impls/AbstractScrollOperator.ts b/src/content/operators/impls/AbstractScrollOperator.ts
new file mode 100644
index 0000000..f8d9f70
--- /dev/null
+++ b/src/content/operators/impls/AbstractScrollOperator.ts
@@ -0,0 +1,10 @@
+import SettingRepository from "../../repositories/SettingRepository";
+
+export default class AbstractScrollOperator {
+ constructor(private readonly settingRepository: SettingRepository) {}
+
+ protected getSmoothScroll(): boolean {
+ const settings = this.settingRepository.get();
+ return settings.properties.smoothscroll;
+ }
+}
diff --git a/src/content/operators/impls/AddonOperatorFactoryChain.ts b/src/content/operators/impls/AddonOperatorFactoryChain.ts
new file mode 100644
index 0000000..54880c4
--- /dev/null
+++ b/src/content/operators/impls/AddonOperatorFactoryChain.ts
@@ -0,0 +1,40 @@
+import { inject, injectable } from "tsyringe";
+import OperatorFactoryChain from "../OperatorFactoryChain";
+import AddonIndicatorClient from "../../client/AddonIndicatorClient";
+import AddonEnabledRepository from "../../repositories/AddonEnabledRepository";
+import * as operations from "../../../shared/operations";
+import Operator from "../Operator";
+import EnableAddonOperator from "./EnableAddonOperator";
+import DisableAddonOperator from "./DisableAddonOperator";
+import ToggleAddonOperator from "./ToggleAddonOperator";
+
+@injectable()
+export default class AddonOperatorFactoryChain implements OperatorFactoryChain {
+ constructor(
+ @inject("AddonIndicatorClient")
+ private readonly addonIndicatorClient: AddonIndicatorClient,
+ @inject("AddonEnabledRepository")
+ private readonly addonEnabledRepository: AddonEnabledRepository
+ ) {}
+
+ create(op: operations.Operation, _repeat: number): Operator | null {
+ switch (op.type) {
+ case operations.ADDON_ENABLE:
+ return new EnableAddonOperator(
+ this.addonIndicatorClient,
+ this.addonEnabledRepository
+ );
+ case operations.ADDON_DISABLE:
+ return new DisableAddonOperator(
+ this.addonIndicatorClient,
+ this.addonEnabledRepository
+ );
+ case operations.ADDON_TOGGLE_ENABLED:
+ return new ToggleAddonOperator(
+ this.addonIndicatorClient,
+ this.addonEnabledRepository
+ );
+ }
+ return null;
+ }
+}
diff --git a/src/content/operators/impls/BackgroundOperationOperator.ts b/src/content/operators/impls/BackgroundOperationOperator.ts
new file mode 100644
index 0000000..dd86559
--- /dev/null
+++ b/src/content/operators/impls/BackgroundOperationOperator.ts
@@ -0,0 +1,15 @@
+import Operator from "../Operator";
+import OperationClient from "../../client/OperationClient";
+import * as operations from "../../../shared/operations";
+
+export default class BackgroundOperationOperator implements Operator {
+ constructor(
+ private readonly operationClient: OperationClient,
+ private readonly repeat: number,
+ private readonly op: operations.Operation
+ ) {}
+
+ async run(): Promise<void> {
+ await this.operationClient.execBackgroundOp(this.repeat, this.op);
+ }
+}
diff --git a/src/content/operators/impls/ClipboardOperatorFactoryChain.ts b/src/content/operators/impls/ClipboardOperatorFactoryChain.ts
new file mode 100644
index 0000000..454aea1
--- /dev/null
+++ b/src/content/operators/impls/ClipboardOperatorFactoryChain.ts
@@ -0,0 +1,47 @@
+import { inject, injectable } from "tsyringe";
+import YankURLOperator from "./YankURLOperator";
+import PasteOperator from "./PasteOperator";
+import Operator from "../Operator";
+import OperatorFactoryChain from "../OperatorFactoryChain";
+import ClipboardRepository from "../../repositories/ClipboardRepository";
+import ConsoleClient from "../../client/ConsoleClient";
+import OperationClient from "../../client/OperationClient";
+import SettingRepository from "../../repositories/SettingRepository";
+import * as operations from "../../../shared/operations";
+import URLRepository from "./URLRepository";
+
+@injectable()
+export default class ClipboardOperatorFactoryChain
+ implements OperatorFactoryChain {
+ constructor(
+ @inject("ClipboardRepository")
+ private readonly clipboardRepository: ClipboardRepository,
+ @inject("ConsoleClient")
+ private readonly consoleClient: ConsoleClient,
+ @inject("OperationClient")
+ private readonly operationClinet: OperationClient,
+ @inject("SettingRepository")
+ private readonly settingRepository: SettingRepository,
+ @inject("URLRepository")
+ private readonly urlRepository: URLRepository
+ ) {}
+
+ create(op: operations.Operation, _repeat: number): Operator | null {
+ switch (op.type) {
+ case operations.URLS_YANK:
+ return new YankURLOperator(
+ this.clipboardRepository,
+ this.consoleClient,
+ this.urlRepository
+ );
+ case operations.URLS_PASTE:
+ return new PasteOperator(
+ this.clipboardRepository,
+ this.settingRepository,
+ this.operationClinet,
+ op.newTab
+ );
+ }
+ return null;
+ }
+}
diff --git a/src/content/operators/impls/DisableAddonOperator.ts b/src/content/operators/impls/DisableAddonOperator.ts
new file mode 100644
index 0000000..28811fe
--- /dev/null
+++ b/src/content/operators/impls/DisableAddonOperator.ts
@@ -0,0 +1,15 @@
+import Operator from "../Operator";
+import AddonIndicatorClient from "../../client/AddonIndicatorClient";
+import AddonEnabledRepository from "../../repositories/AddonEnabledRepository";
+
+export default class DisableAddonOperator implements Operator {
+ constructor(
+ private readonly indicator: AddonIndicatorClient,
+ private readonly repository: AddonEnabledRepository
+ ) {}
+
+ async run(): Promise<void> {
+ this.repository.set(false);
+ await this.indicator.setEnabled(false);
+ }
+}
diff --git a/src/content/operators/impls/EnableAddonOperator.ts b/src/content/operators/impls/EnableAddonOperator.ts
new file mode 100644
index 0000000..b5b1d79
--- /dev/null
+++ b/src/content/operators/impls/EnableAddonOperator.ts
@@ -0,0 +1,15 @@
+import Operator from "../Operator";
+import AddonIndicatorClient from "../../client/AddonIndicatorClient";
+import AddonEnabledRepository from "../../repositories/AddonEnabledRepository";
+
+export default class EnableAddonOperator implements Operator {
+ constructor(
+ private readonly indicator: AddonIndicatorClient,
+ private readonly repository: AddonEnabledRepository
+ ) {}
+
+ async run(): Promise<void> {
+ this.repository.set(true);
+ await this.indicator.setEnabled(true);
+ }
+}
diff --git a/src/content/operators/impls/EnableJumpMarkOperator.ts b/src/content/operators/impls/EnableJumpMarkOperator.ts
new file mode 100644
index 0000000..42ca8ee
--- /dev/null
+++ b/src/content/operators/impls/EnableJumpMarkOperator.ts
@@ -0,0 +1,10 @@
+import Operator from "../Operator";
+import MarkKeyRepository from "../../repositories/MarkKeyRepository";
+
+export default class EnableJumpMarkOperator implements Operator {
+ constructor(private readonly repository: MarkKeyRepository) {}
+
+ async run(): Promise<void> {
+ this.repository.enableJumpMode();
+ }
+}
diff --git a/src/content/operators/impls/EnableSetMarkOperator.ts b/src/content/operators/impls/EnableSetMarkOperator.ts
new file mode 100644
index 0000000..3d0daf4
--- /dev/null
+++ b/src/content/operators/impls/EnableSetMarkOperator.ts
@@ -0,0 +1,10 @@
+import Operator from "../Operator";
+import MarkKeyRepository from "../../repositories/MarkKeyRepository";
+
+export default class EnableSetMarkOperator implements Operator {
+ constructor(private readonly repository: MarkKeyRepository) {}
+
+ async run(): Promise<void> {
+ this.repository.enableSetMode();
+ }
+}
diff --git a/src/content/operators/impls/FindNextOperator.ts b/src/content/operators/impls/FindNextOperator.ts
new file mode 100644
index 0000000..c67f6d9
--- /dev/null
+++ b/src/content/operators/impls/FindNextOperator.ts
@@ -0,0 +1,15 @@
+import Operator from "../Operator";
+import FindMasterClient from "../../client/FindMasterClient";
+
+export default class FindNextOperator implements Operator {
+ constructor(
+ private readonly findMasterClient: FindMasterClient,
+ private readonly repeat: number
+ ) {}
+
+ async run(): Promise<void> {
+ for (let i = 0; i < this.repeat; ++i) {
+ this.findMasterClient.findNext();
+ }
+ }
+}
diff --git a/src/content/operators/impls/FindOperatorFactoryChain.ts b/src/content/operators/impls/FindOperatorFactoryChain.ts
new file mode 100644
index 0000000..b3524c1
--- /dev/null
+++ b/src/content/operators/impls/FindOperatorFactoryChain.ts
@@ -0,0 +1,25 @@
+import { inject, injectable } from "tsyringe";
+import Operator from "../Operator";
+import OperatorFactoryChain from "../OperatorFactoryChain";
+import FindNextOperator from "./FindNextOperator";
+import FindPrevOperator from "./FindPrevOperator";
+import FindMasterClient from "../../client/FindMasterClient";
+import * as operations from "../../../shared/operations";
+
+@injectable()
+export default class FindOperatorFactoryChain implements OperatorFactoryChain {
+ constructor(
+ @inject("FindMasterClient")
+ private readonly findMasterClient: FindMasterClient
+ ) {}
+
+ create(op: operations.Operation, repeat: number): Operator | null {
+ switch (op.type) {
+ case operations.FIND_NEXT:
+ return new FindNextOperator(this.findMasterClient, repeat);
+ case operations.FIND_PREV:
+ return new FindPrevOperator(this.findMasterClient, repeat);
+ }
+ return null;
+ }
+}
diff --git a/src/content/operators/impls/FindPrevOperator.ts b/src/content/operators/impls/FindPrevOperator.ts
new file mode 100644
index 0000000..f73e605
--- /dev/null
+++ b/src/content/operators/impls/FindPrevOperator.ts
@@ -0,0 +1,15 @@
+import Operator from "../Operator";
+import FindMasterClient from "../../client/FindMasterClient";
+
+export default class FindPrevOperator implements Operator {
+ constructor(
+ private readonly findMasterClient: FindMasterClient,
+ private readonly repeat: number
+ ) {}
+
+ async run(): Promise<void> {
+ for (let i = 0; i < this.repeat; ++i) {
+ this.findMasterClient.findPrev();
+ }
+ }
+}
diff --git a/src/content/operators/impls/FocusOperator.ts b/src/content/operators/impls/FocusOperator.ts
new file mode 100644
index 0000000..51d6fec
--- /dev/null
+++ b/src/content/operators/impls/FocusOperator.ts
@@ -0,0 +1,10 @@
+import Operator from "../Operator";
+import FocusPresenter from "../../presenters/FocusPresenter";
+
+export default class FocusOperator implements Operator {
+ constructor(private readonly presenter: FocusPresenter) {}
+
+ async run(): Promise<void> {
+ this.presenter.focusFirstElement();
+ }
+}
diff --git a/src/content/operators/impls/FocusOperatorFactoryChain.ts b/src/content/operators/impls/FocusOperatorFactoryChain.ts
new file mode 100644
index 0000000..c89c1e5
--- /dev/null
+++ b/src/content/operators/impls/FocusOperatorFactoryChain.ts
@@ -0,0 +1,22 @@
+import { inject, injectable } from "tsyringe";
+import OperatorFactoryChain from "../OperatorFactoryChain";
+import Operator from "../Operator";
+import FocusOperator from "./FocusOperator";
+import FocusPresenter from "../../presenters/FocusPresenter";
+import * as operations from "../../../shared/operations";
+
+@injectable()
+export default class FocusOperatorFactoryChain implements OperatorFactoryChain {
+ constructor(
+ @inject("FocusPresenter")
+ private readonly focusPresenter: FocusPresenter
+ ) {}
+
+ create(op: operations.Operation, _repeat: number): Operator | null {
+ switch (op.type) {
+ case operations.FOCUS_INPUT:
+ return new FocusOperator(this.focusPresenter);
+ }
+ return null;
+ }
+}
diff --git a/src/content/operators/impls/FollowOperatorFactoryChain.ts b/src/content/operators/impls/FollowOperatorFactoryChain.ts
new file mode 100644
index 0000000..588e1a4
--- /dev/null
+++ b/src/content/operators/impls/FollowOperatorFactoryChain.ts
@@ -0,0 +1,27 @@
+import { inject, injectable } from "tsyringe";
+import StartFollowOperator from "./StartFollowOperator";
+import Operator from "../Operator";
+import OperatorFactoryChain from "../OperatorFactoryChain";
+import FollowMasterClient from "../../client/FollowMasterClient";
+import * as operations from "../../../shared/operations";
+
+@injectable()
+export default class FollowOperatorFactoryChain
+ implements OperatorFactoryChain {
+ constructor(
+ @inject("FollowMasterClient")
+ private followMasterClient: FollowMasterClient
+ ) {}
+
+ create(op: operations.Operation, _repeat: number): Operator | null {
+ switch (op.type) {
+ case operations.FOLLOW_START:
+ return new StartFollowOperator(
+ this.followMasterClient,
+ op.newTab,
+ op.background
+ );
+ }
+ return null;
+ }
+}
diff --git a/src/content/operators/impls/HorizontalScrollOperator.ts b/src/content/operators/impls/HorizontalScrollOperator.ts
new file mode 100644
index 0000000..f813f85
--- /dev/null
+++ b/src/content/operators/impls/HorizontalScrollOperator.ts
@@ -0,0 +1,21 @@
+import AbstractScrollOperator from "./AbstractScrollOperator";
+import Operator from "../Operator";
+import ScrollPresenter from "../../presenters/ScrollPresenter";
+import SettingRepository from "../../repositories/SettingRepository";
+
+export default class HorizontalScrollOperator
+ extends AbstractScrollOperator
+ implements Operator {
+ constructor(
+ private readonly presenter: ScrollPresenter,
+ settingRepository: SettingRepository,
+ private readonly count: number
+ ) {
+ super(settingRepository);
+ }
+
+ async run(): Promise<void> {
+ const smooth = this.getSmoothScroll();
+ this.presenter.scrollHorizonally(this.count, smooth);
+ }
+}
diff --git a/src/content/operators/impls/MarkOperatorFactoryChain.ts b/src/content/operators/impls/MarkOperatorFactoryChain.ts
new file mode 100644
index 0000000..7e6c513
--- /dev/null
+++ b/src/content/operators/impls/MarkOperatorFactoryChain.ts
@@ -0,0 +1,25 @@
+import { inject, injectable } from "tsyringe";
+import EnableSetMarkOperator from "./EnableSetMarkOperator";
+import EnableJumpMarkOperator from "./EnableJumpMarkOperator";
+import Operator from "../Operator";
+import OperatorFactoryChain from "../OperatorFactoryChain";
+import MarkKeyRepository from "../../repositories/MarkKeyRepository";
+import * as operations from "../../../shared/operations";
+
+@injectable()
+export default class MarkOperatorFactoryChain implements OperatorFactoryChain {
+ constructor(
+ @inject("MarkKeyRepository")
+ private readonly markKeyRepository: MarkKeyRepository
+ ) {}
+
+ create(op: operations.Operation, _repeat: number): Operator | null {
+ switch (op.type) {
+ case operations.MARK_SET_PREFIX:
+ return new EnableSetMarkOperator(this.markKeyRepository);
+ case operations.MARK_JUMP_PREFIX:
+ return new EnableJumpMarkOperator(this.markKeyRepository);
+ }
+ return null;
+ }
+}
diff --git a/src/content/operators/impls/OperatorFactoryImpl.ts b/src/content/operators/impls/OperatorFactoryImpl.ts
new file mode 100644
index 0000000..22b35c8
--- /dev/null
+++ b/src/content/operators/impls/OperatorFactoryImpl.ts
@@ -0,0 +1,51 @@
+import { inject, injectable } from "tsyringe";
+import OperatorFactory from "../OperatorFactory";
+import BackgroundOperationOperator from "./BackgroundOperationOperator";
+import Operator from "../Operator";
+import OperatorFactoryChain from "../OperatorFactoryChain";
+import { Operation } from "../../../shared/operations";
+import OperationClient from "../../client/OperationClient";
+import AddonOperatorFactoryChain from "./AddonOperatorFactoryChain";
+import ClipboardOperatorFactoryChain from "./ClipboardOperatorFactoryChain";
+import FindOperatorFactoryChain from "./FindOperatorFactoryChain";
+import FocusOperatorFactoryChain from "./FocusOperatorFactoryChain";
+import FollowOperatorFactoryChain from "./FollowOperatorFactoryChain";
+import MarkOperatorFactoryChain from "./MarkOperatorFactoryChain";
+import ScrollOperatorFactoryChain from "./ScrollOperatorFactoryChain";
+
+@injectable()
+export default class OperatorFactoryImpl implements OperatorFactory {
+ private readonly factoryChains: OperatorFactoryChain[];
+
+ constructor(
+ addonOperatorFactoryChain: AddonOperatorFactoryChain,
+ clipboardOperatorFactoryChain: ClipboardOperatorFactoryChain,
+ findOperatorFactoryChain: FindOperatorFactoryChain,
+ focusOperatorFactoryChain: FocusOperatorFactoryChain,
+ followOperatorFactoryChain: FollowOperatorFactoryChain,
+ markOperatorFactoryChain: MarkOperatorFactoryChain,
+ scrollOperatorFactoryChain: ScrollOperatorFactoryChain,
+ @inject("OperationClient")
+ private readonly operationClient: OperationClient
+ ) {
+ this.factoryChains = [
+ addonOperatorFactoryChain,
+ clipboardOperatorFactoryChain,
+ findOperatorFactoryChain,
+ focusOperatorFactoryChain,
+ followOperatorFactoryChain,
+ markOperatorFactoryChain,
+ scrollOperatorFactoryChain,
+ ];
+ }
+
+ create(op: Operation, repeat: number): Operator {
+ for (const chain of this.factoryChains) {
+ const operator = chain.create(op, repeat);
+ if (operator != null) {
+ return operator;
+ }
+ }
+ return new BackgroundOperationOperator(this.operationClient, repeat, op);
+ }
+}
diff --git a/src/content/operators/impls/PageScrollOperator.ts b/src/content/operators/impls/PageScrollOperator.ts
new file mode 100644
index 0000000..377bf92
--- /dev/null
+++ b/src/content/operators/impls/PageScrollOperator.ts
@@ -0,0 +1,21 @@
+import AbstractScrollOperator from "./AbstractScrollOperator";
+import Operator from "../Operator";
+import ScrollPresenter from "../../presenters/ScrollPresenter";
+import SettingRepository from "../../repositories/SettingRepository";
+
+export default class PageScrollOperator
+ extends AbstractScrollOperator
+ implements Operator {
+ constructor(
+ private readonly presenter: ScrollPresenter,
+ settingRepository: SettingRepository,
+ private readonly count: number
+ ) {
+ super(settingRepository);
+ }
+
+ async run(): Promise<void> {
+ const smooth = this.getSmoothScroll();
+ this.presenter.scrollPages(this.count, smooth);
+ }
+}
diff --git a/src/content/operators/impls/PasteOperator.ts b/src/content/operators/impls/PasteOperator.ts
new file mode 100644
index 0000000..592da66
--- /dev/null
+++ b/src/content/operators/impls/PasteOperator.ts
@@ -0,0 +1,25 @@
+import Operator from "../Operator";
+import ClipboardRepository from "../../repositories/ClipboardRepository";
+import SettingRepository from "../../repositories/SettingRepository";
+import OperationClient from "../../client/OperationClient";
+import * as urls from "../../../shared/urls";
+
+export default class PasteOperator implements Operator {
+ constructor(
+ private readonly repository: ClipboardRepository,
+ private readonly settingRepository: SettingRepository,
+ private readonly operationClient: OperationClient,
+ private readonly newTab: boolean
+ ) {}
+
+ async run(): Promise<void> {
+ const search = this.settingRepository.get().search;
+ const text = this.repository.read();
+ const url = urls.searchUrl(text, search);
+
+ // NOTE: Repeat pasting from clipboard instead of opening a certain url.
+ // 'Repeat last' command is implemented in the background script and cannot
+ // access to clipboard until Firefox 63.
+ await this.operationClient.internalOpenUrl(url, this.newTab);
+ }
+}
diff --git a/src/content/operators/impls/ScrollOperatorFactoryChain.ts b/src/content/operators/impls/ScrollOperatorFactoryChain.ts
new file mode 100644
index 0000000..6847aea
--- /dev/null
+++ b/src/content/operators/impls/ScrollOperatorFactoryChain.ts
@@ -0,0 +1,68 @@
+import { inject, injectable } from "tsyringe";
+import OperatorFactoryChain from "../OperatorFactoryChain";
+import ScrollPresenter from "../../presenters/ScrollPresenter";
+import SettingRepository from "../../repositories/SettingRepository";
+import * as operations from "../../../shared/operations";
+import Operator from "../Operator";
+import VerticalScrollOperator from "./VerticalScrollOperator";
+import HorizontalScrollOperator from "./HorizontalScrollOperator";
+import PageScrollOperator from "./PageScrollOperator";
+import ScrollToTopOperator from "./ScrollToTopOperator";
+import ScrollToBottomOperator from "./ScrollToBottomOperator";
+import ScrollToHomeOperator from "./ScrollToHomeOperator";
+import ScrollToEndOperator from "./ScrollToEndOperator";
+
+@injectable()
+export default class ScrollOperatorFactoryChain
+ implements OperatorFactoryChain {
+ constructor(
+ @inject("ScrollPresenter")
+ private readonly scrollPresenter: ScrollPresenter,
+ @inject("SettingRepository")
+ private readonly settingRepository: SettingRepository
+ ) {}
+
+ create(op: operations.Operation, repeat: number): Operator | null {
+ switch (op.type) {
+ case operations.SCROLL_VERTICALLY:
+ return new VerticalScrollOperator(
+ this.scrollPresenter,
+ this.settingRepository,
+ op.count * repeat
+ );
+ case operations.SCROLL_HORIZONALLY:
+ return new HorizontalScrollOperator(
+ this.scrollPresenter,
+ this.settingRepository,
+ op.count * repeat
+ );
+ case operations.SCROLL_PAGES:
+ return new PageScrollOperator(
+ this.scrollPresenter,
+ this.settingRepository,
+ op.count * repeat
+ );
+ case operations.SCROLL_TOP:
+ return new ScrollToTopOperator(
+ this.scrollPresenter,
+ this.settingRepository
+ );
+ case operations.SCROLL_BOTTOM:
+ return new ScrollToBottomOperator(
+ this.scrollPresenter,
+ this.settingRepository
+ );
+ case operations.SCROLL_HOME:
+ return new ScrollToHomeOperator(
+ this.scrollPresenter,
+ this.settingRepository
+ );
+ case operations.SCROLL_END:
+ return new ScrollToEndOperator(
+ this.scrollPresenter,
+ this.settingRepository
+ );
+ }
+ return null;
+ }
+}
diff --git a/src/content/operators/impls/ScrollToBottomOperator.ts b/src/content/operators/impls/ScrollToBottomOperator.ts
new file mode 100644
index 0000000..4db521b
--- /dev/null
+++ b/src/content/operators/impls/ScrollToBottomOperator.ts
@@ -0,0 +1,20 @@
+import AbstractScrollOperator from "./AbstractScrollOperator";
+import Operator from "../Operator";
+import ScrollPresenter from "../../presenters/ScrollPresenter";
+import SettingRepository from "../../repositories/SettingRepository";
+
+export default class ScrollToBottomOperator
+ extends AbstractScrollOperator
+ implements Operator {
+ constructor(
+ private readonly presenter: ScrollPresenter,
+ settingRepository: SettingRepository
+ ) {
+ super(settingRepository);
+ }
+
+ async run(): Promise<void> {
+ const smooth = this.getSmoothScroll();
+ this.presenter.scrollToBottom(smooth);
+ }
+}
diff --git a/src/content/operators/impls/ScrollToEndOperator.ts b/src/content/operators/impls/ScrollToEndOperator.ts
new file mode 100644
index 0000000..8217e15
--- /dev/null
+++ b/src/content/operators/impls/ScrollToEndOperator.ts
@@ -0,0 +1,20 @@
+import AbstractScrollOperator from "./AbstractScrollOperator";
+import Operator from "../Operator";
+import ScrollPresenter from "../../presenters/ScrollPresenter";
+import SettingRepository from "../../repositories/SettingRepository";
+
+export default class ScrollToEndOperator
+ extends AbstractScrollOperator
+ implements Operator {
+ constructor(
+ private readonly presenter: ScrollPresenter,
+ settingRepository: SettingRepository
+ ) {
+ super(settingRepository);
+ }
+
+ async run(): Promise<void> {
+ const smooth = this.getSmoothScroll();
+ this.presenter.scrollToEnd(smooth);
+ }
+}
diff --git a/src/content/operators/impls/ScrollToHomeOperator.ts b/src/content/operators/impls/ScrollToHomeOperator.ts
new file mode 100644
index 0000000..a0d7701
--- /dev/null
+++ b/src/content/operators/impls/ScrollToHomeOperator.ts
@@ -0,0 +1,20 @@
+import AbstractScrollOperator from "./AbstractScrollOperator";
+import Operator from "../Operator";
+import ScrollPresenter from "../../presenters/ScrollPresenter";
+import SettingRepository from "../../repositories/SettingRepository";
+
+export default class ScrollToHomeOperator
+ extends AbstractScrollOperator
+ implements Operator {
+ constructor(
+ private readonly presenter: ScrollPresenter,
+ settingRepository: SettingRepository
+ ) {
+ super(settingRepository);
+ }
+
+ async run(): Promise<void> {
+ const smooth = this.getSmoothScroll();
+ this.presenter.scrollToHome(smooth);
+ }
+}
diff --git a/src/content/operators/impls/ScrollToTopOperator.ts b/src/content/operators/impls/ScrollToTopOperator.ts
new file mode 100644
index 0000000..6075758
--- /dev/null
+++ b/src/content/operators/impls/ScrollToTopOperator.ts
@@ -0,0 +1,20 @@
+import AbstractScrollOperator from "./AbstractScrollOperator";
+import Operator from "../Operator";
+import ScrollPresenter from "../../presenters/ScrollPresenter";
+import SettingRepository from "../../repositories/SettingRepository";
+
+export default class ScrollToTopOperator
+ extends AbstractScrollOperator
+ implements Operator {
+ constructor(
+ private readonly presenter: ScrollPresenter,
+ settingRepository: SettingRepository
+ ) {
+ super(settingRepository);
+ }
+
+ async run(): Promise<void> {
+ const smooth = this.getSmoothScroll();
+ this.presenter.scrollToTop(smooth);
+ }
+}
diff --git a/src/content/operators/impls/StartFollowOperator.ts b/src/content/operators/impls/StartFollowOperator.ts
new file mode 100644
index 0000000..6f30058
--- /dev/null
+++ b/src/content/operators/impls/StartFollowOperator.ts
@@ -0,0 +1,14 @@
+import Operator from "../Operator";
+import FollowMasterClient from "../../client/FollowMasterClient";
+
+export default class StartFollowOperator implements Operator {
+ constructor(
+ private readonly followMasterClient: FollowMasterClient,
+ private readonly newTab: boolean,
+ private readonly background: boolean
+ ) {}
+
+ async run(): Promise<void> {
+ this.followMasterClient.startFollow(this.newTab, this.background);
+ }
+}
diff --git a/src/content/operators/impls/ToggleAddonOperator.ts b/src/content/operators/impls/ToggleAddonOperator.ts
new file mode 100644
index 0000000..2a249d6
--- /dev/null
+++ b/src/content/operators/impls/ToggleAddonOperator.ts
@@ -0,0 +1,16 @@
+import Operator from "../Operator";
+import AddonIndicatorClient from "../../client/AddonIndicatorClient";
+import AddonEnabledRepository from "../../repositories/AddonEnabledRepository";
+
+export default class ToggleAddonOperator implements Operator {
+ constructor(
+ private readonly indicator: AddonIndicatorClient,
+ private readonly repository: AddonEnabledRepository
+ ) {}
+
+ async run(): Promise<void> {
+ const current = this.repository.get();
+ this.repository.set(!current);
+ await this.indicator.setEnabled(!current);
+ }
+}
diff --git a/src/content/operators/impls/URLRepository.ts b/src/content/operators/impls/URLRepository.ts
new file mode 100644
index 0000000..a1efc2e
--- /dev/null
+++ b/src/content/operators/impls/URLRepository.ts
@@ -0,0 +1,9 @@
+export default interface URLRepository {
+ getCurrentURL(): string;
+}
+
+export class URLRepositoryImpl implements URLRepository {
+ getCurrentURL(): string {
+ return window.location.href;
+ }
+}
diff --git a/src/content/operators/impls/VerticalScrollOperator.ts b/src/content/operators/impls/VerticalScrollOperator.ts
new file mode 100644
index 0000000..4ab336c
--- /dev/null
+++ b/src/content/operators/impls/VerticalScrollOperator.ts
@@ -0,0 +1,21 @@
+import AbstractScrollOperator from "./AbstractScrollOperator";
+import Operator from "../Operator";
+import ScrollPresenter from "../../presenters/ScrollPresenter";
+import SettingRepository from "../../repositories/SettingRepository";
+
+export default class VerticalScrollOperator
+ extends AbstractScrollOperator
+ implements Operator {
+ constructor(
+ private readonly presenter: ScrollPresenter,
+ settingRepository: SettingRepository,
+ private readonly count: number
+ ) {
+ super(settingRepository);
+ }
+
+ async run(): Promise<void> {
+ const smooth = this.getSmoothScroll();
+ this.presenter.scrollVertically(this.count, smooth);
+ }
+}
diff --git a/src/content/operators/impls/YankURLOperator.ts b/src/content/operators/impls/YankURLOperator.ts
new file mode 100644
index 0000000..2e774eb
--- /dev/null
+++ b/src/content/operators/impls/YankURLOperator.ts
@@ -0,0 +1,18 @@
+import Operator from "../Operator";
+import ClipboardRepository from "../../repositories/ClipboardRepository";
+import ConsoleClient from "../../client/ConsoleClient";
+import URLRepository from "./URLRepository";
+
+export default class YankURLOperator implements Operator {
+ constructor(
+ private readonly repository: ClipboardRepository,
+ private readonly consoleClient: ConsoleClient,
+ private readonly urlRepository: URLRepository
+ ) {}
+
+ async run(): Promise<void> {
+ const url = this.urlRepository.getCurrentURL();
+ this.repository.write(url);
+ await this.consoleClient.info("Yanked " + url);
+ }
+}
diff --git a/src/content/presenters/ScrollPresenter.ts b/src/content/presenters/ScrollPresenter.ts
index a78e4ba..dc57d20 100644
--- a/src/content/presenters/ScrollPresenter.ts
+++ b/src/content/presenters/ScrollPresenter.ts
@@ -26,7 +26,7 @@ const doneScrolling = (element: Element): boolean => {
);
};
-// Find a visiable and scrollable element by depth-first search. Currently
+// Find a visible and scrollable element by depth-first search. Currently
// this method is called by each scrolling, and the returned value of this
// method is not cached. That does not cause performance issue because in the
// most pages, the window is root element i,e, documentElement.
diff --git a/src/content/repositories/HintKeyRepository.ts b/src/content/repositories/HintKeyRepository.ts
new file mode 100644
index 0000000..342b003
--- /dev/null
+++ b/src/content/repositories/HintKeyRepository.ts
@@ -0,0 +1,49 @@
+export default interface HintKeyRepository {
+ reset(charset: string): void;
+
+ produce(): string;
+}
+
+const current: {
+ charset: string;
+ counter: number[];
+} = {
+ charset: "",
+ counter: [],
+};
+
+export class HintKeyRepositoryImpl implements HintKeyRepository {
+ reset(charset: string): void {
+ if (charset.length === 0) {
+ throw new TypeError("charset is empty");
+ }
+ current.charset = charset;
+ current.counter = [];
+ }
+
+ produce(): string {
+ if (current.charset === "") {
+ throw new Error("charset is not set");
+ }
+ this.increment();
+
+ return current.counter.map((x) => current.charset[x]).join("");
+ }
+
+ private increment(): void {
+ const max = current.charset.length - 1;
+ if (current.counter.every((x) => x === max)) {
+ current.counter = new Array(current.counter.length + 1).fill(0);
+ return;
+ }
+
+ current.counter.reverse();
+ const len = current.charset.length;
+ let num = current.counter.reduce((x, y, index) => x + y * len ** index) + 1;
+ for (let i = 0; i < current.counter.length; ++i) {
+ current.counter[i] = num % len;
+ num = ~~(num / len);
+ }
+ current.counter.reverse();
+ }
+}
diff --git a/src/content/usecases/ClipboardUseCase.ts b/src/content/usecases/ClipboardUseCase.ts
deleted file mode 100644
index 875fc11..0000000
--- a/src/content/usecases/ClipboardUseCase.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { injectable, inject } from "tsyringe";
-import * as urls from "../../shared/urls";
-import ClipboardRepository from "../repositories/ClipboardRepository";
-import SettingRepository from "../repositories/SettingRepository";
-import ConsoleClient from "../client/ConsoleClient";
-import OperationClient from "../client/OperationClient";
-
-@injectable()
-export default class ClipboardUseCase {
- constructor(
- @inject("ClipboardRepository") private repository: ClipboardRepository,
- @inject("SettingRepository") private settingRepository: SettingRepository,
- @inject("ConsoleClient") private consoleClient: ConsoleClient,
- @inject("OperationClient") private operationClinet: OperationClient
- ) {}
-
- async yankCurrentURL(): Promise<string> {
- const url = window.location.href;
- this.repository.write(url);
- await this.consoleClient.info("Yanked " + url);
- return Promise.resolve(url);
- }
-
- async openOrSearch(newTab: boolean): Promise<void> {
- const search = this.settingRepository.get().search;
- const text = this.repository.read();
- const url = urls.searchUrl(text, search);
-
- // TODO: Repeat pasting from clipboard instead of opening a certain url.
- // 'Repeat last' command is implemented in the background script and cannot
- // access to clipboard until Firefox 63.
- await this.operationClinet.internalOpenUrl(url, newTab);
- }
-}
diff --git a/src/content/usecases/FindSlaveUseCase.ts b/src/content/usecases/FindSlaveUseCase.ts
deleted file mode 100644
index 3b8c4b4..0000000
--- a/src/content/usecases/FindSlaveUseCase.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { injectable, inject } from "tsyringe";
-import FindMasterClient from "../client/FindMasterClient";
-
-@injectable()
-export default class FindSlaveUseCase {
- constructor(
- @inject("FindMasterClient") private findMasterClient: FindMasterClient
- ) {}
-
- findNext() {
- this.findMasterClient.findNext();
- }
-
- findPrev() {
- this.findMasterClient.findPrev();
- }
-}
diff --git a/src/content/usecases/FocusUseCase.ts b/src/content/usecases/FocusUseCase.ts
deleted file mode 100644
index 8c62003..0000000
--- a/src/content/usecases/FocusUseCase.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { injectable, inject } from "tsyringe";
-import FocusPresenter from "../presenters/FocusPresenter";
-
-@injectable()
-export default class FocusUseCases {
- constructor(@inject("FocusPresenter") private presenter: FocusPresenter) {}
-
- focusFirstInput() {
- this.presenter.focusFirstElement();
- }
-}
diff --git a/src/content/usecases/FollowMasterUseCase.ts b/src/content/usecases/FollowMasterUseCase.ts
index 88c682e..f4705e1 100644
--- a/src/content/usecases/FollowMasterUseCase.ts
+++ b/src/content/usecases/FollowMasterUseCase.ts
@@ -4,13 +4,10 @@ import FollowMasterRepository from "../repositories/FollowMasterRepository";
import FollowSlaveClient from "../client/FollowSlaveClient";
import FollowSlaveClientFactory from "../client/FollowSlaveClientFactory";
import SettingRepository from "../repositories/SettingRepository";
-import HintKeyProducer from "./HintKeyProducer";
+import HintKeyRepository from "../repositories/HintKeyRepository";
@injectable()
export default class FollowMasterUseCase {
- // TODO Make repository
- private producer: HintKeyProducer | null;
-
constructor(
@inject("FollowKeyRepository")
private followKeyRepository: FollowKeyRepository,
@@ -22,14 +19,15 @@ export default class FollowMasterUseCase {
private settingRepository: SettingRepository,
@inject("FollowSlaveClientFactory")
- private followSlaveClientFactory: FollowSlaveClientFactory
- ) {
- this.producer = null;
- }
+ private followSlaveClientFactory: FollowSlaveClientFactory,
+
+ @inject("HintKeyRepository")
+ private hintKeyRepository: HintKeyRepository
+ ) {}
startFollow(newTab: boolean, background: boolean): void {
const hintchars = this.settingRepository.get().properties.hintchars;
- this.producer = new HintKeyProducer(hintchars);
+ this.hintKeyRepository.reset(hintchars);
this.followKeyRepository.clearKeys();
this.followMasterRepository.setCurrentFollowMode(newTab, background);
@@ -59,7 +57,7 @@ export default class FollowMasterUseCase {
createSlaveHints(count: number, sender: Window): void {
const produced = [];
for (let i = 0; i < count; ++i) {
- const tag = this.producer!.produce();
+ const tag = this.hintKeyRepository.produce();
produced.push(tag);
this.followMasterRepository.addTag(tag);
}
diff --git a/src/content/usecases/HintKeyProducer.ts b/src/content/usecases/HintKeyProducer.ts
deleted file mode 100644
index a5e2877..0000000
--- a/src/content/usecases/HintKeyProducer.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-export default class HintKeyProducer {
- private charset: string;
-
- private counter: number[];
-
- constructor(charset: string) {
- if (charset.length === 0) {
- throw new TypeError("charset is empty");
- }
-
- this.charset = charset;
- this.counter = [];
- }
-
- produce(): string {
- this.increment();
-
- return this.counter.map((x) => this.charset[x]).join("");
- }
-
- private increment(): void {
- const max = this.charset.length - 1;
- if (this.counter.every((x) => x === max)) {
- this.counter = new Array(this.counter.length + 1).fill(0);
- return;
- }
-
- this.counter.reverse();
- const len = this.charset.length;
- let num = this.counter.reduce((x, y, index) => x + y * len ** index) + 1;
- for (let i = 0; i < this.counter.length; ++i) {
- this.counter[i] = num % len;
- num = ~~(num / len);
- }
- this.counter.reverse();
- }
-}
diff --git a/src/content/usecases/MarkKeyUseCase.ts b/src/content/usecases/MarkKeyUseCase.ts
index b807c74..61ed135 100644
--- a/src/content/usecases/MarkKeyUseCase.ts
+++ b/src/content/usecases/MarkKeyUseCase.ts
@@ -15,18 +15,10 @@ export default class MarkKeyUseCase {
return this.repository.isJumpMode();
}
- enableSetMode(): void {
- this.repository.enableSetMode();
- }
-
disableSetMode(): void {
this.repository.disabeSetMode();
}
- enableJumpMode(): void {
- this.repository.enableJumpMode();
- }
-
disableJumpMode(): void {
this.repository.disabeJumpMode();
}
diff --git a/src/content/usecases/ScrollUseCase.ts b/src/content/usecases/ScrollUseCase.ts
deleted file mode 100644
index 319c8b4..0000000
--- a/src/content/usecases/ScrollUseCase.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { injectable, inject } from "tsyringe";
-import ScrollPresenter from "../presenters/ScrollPresenter";
-import SettingRepository from "../repositories/SettingRepository";
-
-@injectable()
-export default class ScrollUseCase {
- constructor(
- @inject("ScrollPresenter") private presenter: ScrollPresenter,
- @inject("SettingRepository") private settingRepository: SettingRepository
- ) {}
-
- scrollVertically(count: number): void {
- const smooth = this.getSmoothScroll();
- this.presenter.scrollVertically(count, smooth);
- }
-
- scrollHorizonally(count: number): void {
- const smooth = this.getSmoothScroll();
- this.presenter.scrollHorizonally(count, smooth);
- }
-
- scrollPages(count: number): void {
- const smooth = this.getSmoothScroll();
- this.presenter.scrollPages(count, smooth);
- }
-
- scrollToTop(): void {
- const smooth = this.getSmoothScroll();
- this.presenter.scrollToTop(smooth);
- }
-
- scrollToBottom(): void {
- const smooth = this.getSmoothScroll();
- this.presenter.scrollToBottom(smooth);
- }
-
- scrollToHome(): void {
- const smooth = this.getSmoothScroll();
- this.presenter.scrollToHome(smooth);
- }
-
- scrollToEnd(): void {
- const smooth = this.getSmoothScroll();
- this.presenter.scrollToEnd(smooth);
- }
-
- private getSmoothScroll(): boolean {
- const settings = this.settingRepository.get();
- return settings.properties.smoothscroll;
- }
-}
diff --git a/test/background/mock/MockBrowserSettingRepository.ts b/test/background/mock/MockBrowserSettingRepository.ts
new file mode 100644
index 0000000..22e7084
--- /dev/null
+++ b/test/background/mock/MockBrowserSettingRepository.ts
@@ -0,0 +1,10 @@
+import BrowserSettingRepository from "../../../src/background/repositories/BrowserSettingRepository";
+
+export default class MockBrowserSettingRepository
+ implements BrowserSettingRepository {
+ constructor(private readonly homepageUrls: string[]) {}
+
+ getHomepageUrls(): Promise<string[]> {
+ return Promise.resolve(this.homepageUrls);
+ }
+}
diff --git a/test/background/mock/MockConsoleClient.ts b/test/background/mock/MockConsoleClient.ts
new file mode 100644
index 0000000..d1f8fc3
--- /dev/null
+++ b/test/background/mock/MockConsoleClient.ts
@@ -0,0 +1,23 @@
+import ConsoleClient from "../../../src/background/infrastructures/ConsoleClient";
+
+export default class MockConsoleClient implements ConsoleClient {
+ hide(_tabId: number): Promise<any> {
+ throw new Error("not implemented");
+ }
+
+ showCommand(_tabId: number, _command: string): Promise<any> {
+ throw new Error("not implemented");
+ }
+
+ showError(_tabId: number, _message: string): Promise<any> {
+ throw new Error("not implemented");
+ }
+
+ showFind(_tabId: number): Promise<any> {
+ throw new Error("not implemented");
+ }
+
+ showInfo(_tabId: number, _message: string): Promise<any> {
+ throw new Error("not implemented");
+ }
+}
diff --git a/test/background/mock/MockNavigateClient.ts b/test/background/mock/MockNavigateClient.ts
new file mode 100644
index 0000000..d9442a4
--- /dev/null
+++ b/test/background/mock/MockNavigateClient.ts
@@ -0,0 +1,19 @@
+import NavigateClient from "../../../src/background/clients/NavigateClient";
+
+export default class MockNavigateClient implements NavigateClient {
+ historyNext(_tabId: number): Promise<void> {
+ throw new Error("not implemented");
+ }
+
+ historyPrev(_tabId: number): Promise<void> {
+ throw new Error("not implemented");
+ }
+
+ linkNext(_tabId: number): Promise<void> {
+ throw new Error("not implemented");
+ }
+
+ linkPrev(_tabId: number): Promise<void> {
+ throw new Error("not implemented");
+ }
+}
diff --git a/test/background/mock/MockRepeatRepository.ts b/test/background/mock/MockRepeatRepository.ts
new file mode 100644
index 0000000..1a686c8
--- /dev/null
+++ b/test/background/mock/MockRepeatRepository.ts
@@ -0,0 +1,14 @@
+import RepeatRepository from "../../../src/background/repositories/RepeatRepository";
+import { Operation } from "../../../src/shared/operations";
+
+export default class MockRepeatRepository implements RepeatRepository {
+ private op: Operation | undefined = undefined;
+
+ getLastOperation(): Operation | undefined {
+ return this.op;
+ }
+
+ setLastOperation(op: Operation): void {
+ this.op = op;
+ }
+}
diff --git a/test/background/mock/MockTabPresenter.ts b/test/background/mock/MockTabPresenter.ts
new file mode 100644
index 0000000..22fb947
--- /dev/null
+++ b/test/background/mock/MockTabPresenter.ts
@@ -0,0 +1,179 @@
+import TabPresenter from "../../../src/background/presenters/TabPresenter";
+
+export default class MockTabPresenter implements TabPresenter {
+ private readonly tabs: browser.tabs.Tab[] = [];
+ private readonly zooms: number[] = [];
+ private nextid = 0;
+
+ private readonly lastSelectedId: number | undefined;
+
+ private static defaultTabOptions = {
+ hidden: false,
+ highlighted: false,
+ incognito: false,
+ isArticle: false,
+ isInReaderMode: false,
+ lastAccessed: 0,
+ pinned: false,
+ selected: false,
+ windowId: 0,
+ };
+
+ create(
+ url: string,
+ opts?: {
+ active?: boolean;
+ cookieStoreId?: string;
+ index?: number;
+ openerTabId?: number;
+ pinned?: boolean;
+ windowId?: number;
+ }
+ ): Promise<browser.tabs.Tab> {
+ const tab = {
+ ...MockTabPresenter.defaultTabOptions,
+ ...opts,
+ id: this.nextid++,
+ active: false,
+ title: "welcome, world",
+ url,
+ index: this.tabs.length,
+ };
+ if (opts?.active || this.tabs.length === 0) {
+ this.tabs.forEach((t) => (t.active = false));
+ tab.active = true;
+ }
+ this.tabs.push(tab);
+ this.zooms.push(1);
+ return Promise.resolve(tab);
+ }
+
+ duplicate(id: number): Promise<browser.tabs.Tab> {
+ const src = this.tabs.find((t) => t.id === id);
+ if (!src) {
+ throw new Error(`tab ${id} not found`);
+ }
+ this.tabs.forEach((t) => (t.active = false));
+ const tab = { ...src, id: this.nextid++, active: true };
+ this.tabs.push(tab);
+ this.zooms.push(1);
+
+ return Promise.resolve(tab);
+ }
+
+ getAll(): Promise<browser.tabs.Tab[]> {
+ return Promise.resolve([...this.tabs]);
+ }
+
+ getByKeyword(
+ keyword: string,
+ excludePinned: boolean
+ ): Promise<browser.tabs.Tab[]> {
+ const tabs = this.tabs
+
+ .filter((t) => {
+ return (
+ (t.url && t.url.toLowerCase().includes(keyword.toLowerCase())) ||
+ (t.title && t.title.toLowerCase().includes(keyword.toLowerCase()))
+ );
+ })
+ .filter((t) => {
+ return !(excludePinned && t.pinned);
+ });
+ return Promise.resolve(tabs);
+ }
+
+ getCurrent(): Promise<browser.tabs.Tab> {
+ const tab = this.tabs.find((t) => t.active);
+ if (!tab) {
+ throw new Error("active tab not found");
+ }
+ return Promise.resolve(tab);
+ }
+
+ getLastSelectedId(): Promise<number | undefined> {
+ return Promise.resolve(this.lastSelectedId);
+ }
+
+ getZoom(tabId: number): Promise<number> {
+ const index = this.tabs.findIndex((t) => t.id === tabId);
+ if (index === -1) {
+ throw new Error(`tab ${tabId} not found`);
+ }
+ return Promise.resolve(this.zooms[index]);
+ }
+
+ onSelected(
+ _listener: (arg: { tabId: number; windowId: number }) => void
+ ): void {
+ throw new Error("not implemented");
+ }
+
+ open(url: string, tabId?: number): Promise<browser.tabs.Tab> {
+ let tab = this.tabs.find((t) => t.active);
+ if (!tab) {
+ throw new Error(`active tab not found`);
+ }
+ if (tabId !== undefined) {
+ tab = this.tabs.find((t) => t.id === tabId);
+ }
+ if (!tab) {
+ throw new Error(`tab ${tabId} not found`);
+ }
+ tab.url = url;
+ return Promise.resolve(tab);
+ }
+
+ reload(_tabId: number, _cache: boolean): Promise<void> {
+ throw new Error("not implemented");
+ }
+
+ remove(ids: number[]): Promise<void> {
+ for (const id of ids) {
+ const index = this.tabs.findIndex((t) => t.id === id);
+ if (index === -1) {
+ throw new Error(`tab ${id} not found`);
+ }
+ const tab = this.tabs[index];
+ this.tabs.splice(index, 1);
+ this.zooms.splice(index, 1);
+ if (tab.active) {
+ this.tabs[Math.min(index, this.tabs.length - 1)].active = true;
+ }
+ }
+
+ return Promise.resolve(undefined);
+ }
+
+ reopen(): Promise<void> {
+ throw new Error("not implemented");
+ }
+
+ select(tabId: number): Promise<void> {
+ const tab = this.tabs.find((t) => t.id === tabId);
+ if (!tab) {
+ throw new Error(`tab ${tabId} not found`);
+ }
+ this.tabs.forEach((t) => (t.active = false));
+ tab.active = true;
+ return Promise.resolve(undefined);
+ }
+
+ setPinned(tabId: number, pinned: boolean): Promise<void> {
+ const tab = this.tabs.find((t) => t.id === tabId);
+ if (!tab) {
+ throw new Error(`tab ${tabId} not found`);
+ }
+ tab.pinned = pinned;
+ return Promise.resolve();
+ }
+
+ setZoom(tabId: number, factor: number): Promise<void> {
+ const index = this.tabs.findIndex((t) => t.id === tabId);
+ if (index === -1) {
+ throw new Error(`tab ${tabId} not found`);
+ }
+ this.zooms[index] = factor;
+ return Promise.resolve();
+ }
+}
diff --git a/test/background/mock/MockWindowPresenter.ts b/test/background/mock/MockWindowPresenter.ts
new file mode 100644
index 0000000..420ae8b
--- /dev/null
+++ b/test/background/mock/MockWindowPresenter.ts
@@ -0,0 +1,7 @@
+import WindowPresenter from "../../../src/background/presenters/WindowPresenter";
+
+export default class MockWindowPresenter implements WindowPresenter {
+ create(_url: string): Promise<void> {
+ throw new Error("not implemented");
+ }
+}
diff --git a/test/background/mock/MockZoomPresenter.ts b/test/background/mock/MockZoomPresenter.ts
new file mode 100644
index 0000000..53d1980
--- /dev/null
+++ b/test/background/mock/MockZoomPresenter.ts
@@ -0,0 +1,15 @@
+import ZoomPresenter from "../../../src/background/presenters/ZoomPresenter";
+
+export default class MockZoomPresenter implements ZoomPresenter {
+ resetZoom(): Promise<void> {
+ throw new Error("not implemented");
+ }
+
+ zoomIn(): Promise<void> {
+ throw new Error("not implemented");
+ }
+
+ zoomOut(): Promise<void> {
+ throw new Error("not implemented");
+ }
+}
diff --git a/test/background/operators/impls/CancelOperator.test.ts b/test/background/operators/impls/CancelOperator.test.ts
new file mode 100644
index 0000000..915becf
--- /dev/null
+++ b/test/background/operators/impls/CancelOperator.test.ts
@@ -0,0 +1,24 @@
+import sinon from "sinon";
+import CancelOperator from "../../../../src/background/operators/impls/CancelOperator";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+import MockConsoleClient from "../../mock/MockConsoleClient";
+
+describe("CancelOperator", () => {
+ describe("#run", () => {
+ it("hides console", async () => {
+ const tabPresenter = new MockTabPresenter();
+ const currenTab = await tabPresenter.create("https://example.com/");
+
+ const consoleClient = new MockConsoleClient();
+ const mock = sinon
+ .mock(consoleClient)
+ .expects("hide")
+ .withArgs(currenTab?.id);
+ const sut = new CancelOperator(tabPresenter, consoleClient);
+
+ await sut.run();
+
+ mock.verify();
+ });
+ });
+});
diff --git a/test/background/operators/impls/CloseTabOperator.test.ts b/test/background/operators/impls/CloseTabOperator.test.ts
new file mode 100644
index 0000000..ba9cbfe
--- /dev/null
+++ b/test/background/operators/impls/CloseTabOperator.test.ts
@@ -0,0 +1,61 @@
+import { expect } from "chai";
+import CloseTabOperator from "../../../../src/background/operators/impls/CloseTabOperator";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+
+describe("CloseTabOperator", () => {
+ describe("#run", () => {
+ it("close a current tab", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: true });
+ await tabPresenter.create("https://example.com/3", { active: false });
+ const sut = new CloseTabOperator(tabPresenter);
+
+ await sut.run();
+
+ const tabs = await tabPresenter.getAll();
+ expect(tabs.map((t) => t.url)).to.deep.equal([
+ "https://example.com/1",
+ "https://example.com/3",
+ ]);
+ });
+
+ it("close a current tab forcely", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", {
+ pinned: true,
+ active: false,
+ });
+ await tabPresenter.create("https://example.com/2", {
+ pinned: true,
+ active: true,
+ });
+ await tabPresenter.create("https://example.com/3", {
+ pinned: true,
+ active: false,
+ });
+ const sut = new CloseTabOperator(tabPresenter, true);
+
+ await sut.run();
+
+ const tabs = await tabPresenter.getAll();
+ expect(tabs.map((t) => t.url)).to.deep.equal([
+ "https://example.com/1",
+ "https://example.com/3",
+ ]);
+ });
+
+ it("close a current tab and select left of the closed tab", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: true });
+ await tabPresenter.create("https://example.com/3", { active: false });
+ const sut = new CloseTabOperator(tabPresenter, false, true);
+
+ await sut.run();
+
+ const tab = await tabPresenter.getCurrent();
+ expect(tab.url).to.equal("https://example.com/1");
+ });
+ });
+});
diff --git a/test/background/operators/impls/CloseTabRightOperator.test.ts b/test/background/operators/impls/CloseTabRightOperator.test.ts
new file mode 100644
index 0000000..c2a106c
--- /dev/null
+++ b/test/background/operators/impls/CloseTabRightOperator.test.ts
@@ -0,0 +1,24 @@
+import { expect } from "chai";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+import CloseTabRightOperator from "../../../../src/background/operators/impls/CloseTabRightOperator";
+
+describe("CloseTabRightOperator", () => {
+ describe("#run", () => {
+ it("close the right of the current tab", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: true });
+ await tabPresenter.create("https://example.com/3", { active: false });
+ await tabPresenter.create("https://example.com/4", { active: false });
+ const sut = new CloseTabRightOperator(tabPresenter);
+
+ await sut.run();
+
+ const tabs = await tabPresenter.getAll();
+ expect(tabs.map((t) => t.url)).to.deep.equal([
+ "https://example.com/1",
+ "https://example.com/2",
+ ]);
+ });
+ });
+});
diff --git a/test/background/operators/impls/CommandOperatorFactoryChain.test.ts b/test/background/operators/impls/CommandOperatorFactoryChain.test.ts
new file mode 100644
index 0000000..e481c5a
--- /dev/null
+++ b/test/background/operators/impls/CommandOperatorFactoryChain.test.ts
@@ -0,0 +1,42 @@
+import "reflect-metadata";
+import { expect } from "chai";
+import CommandOperatorFactoryChain from "../../../../src/background/operators/impls/CommandOperatorFactoryChain";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+import MockConsoleClient from "../../mock/MockConsoleClient";
+import * as operations from "../../../../src/shared/operations";
+import ShowCommandOperator from "../../../../src/background/operators/impls/ShowCommandOperator";
+import ShowTabOpenCommandOperator from "../../../../src/background/operators/impls/ShowTabOpenCommandOperator";
+import ShowWinOpenCommandOperator from "../../../../src/background/operators/impls/ShowWinOpenCommandOperator";
+import ShowBufferCommandOperator from "../../../../src/background/operators/impls/ShowBufferCommandOperator";
+import ShowAddBookmarkOperator from "../../../../src/background/operators/impls/ShowAddBookmarkOperator";
+import StartFindOperator from "../../../../src/background/operators/impls/StartFindOperator";
+
+describe("CommandOperatorFactoryChain", () => {
+ describe("#create", () => {
+ it("returns a operator for the operation", async () => {
+ const tabPresenter = new MockTabPresenter();
+ const consoleClient = new MockConsoleClient();
+ const sut = new CommandOperatorFactoryChain(tabPresenter, consoleClient);
+
+ expect(sut.create({ type: operations.COMMAND_SHOW })).to.be.instanceOf(
+ ShowCommandOperator
+ );
+ expect(
+ sut.create({ type: operations.COMMAND_SHOW_TABOPEN, alter: true })
+ ).to.be.instanceOf(ShowTabOpenCommandOperator);
+ expect(
+ sut.create({ type: operations.COMMAND_SHOW_WINOPEN, alter: true })
+ ).to.be.instanceOf(ShowWinOpenCommandOperator);
+ expect(
+ sut.create({ type: operations.COMMAND_SHOW_BUFFER })
+ ).to.be.instanceOf(ShowBufferCommandOperator);
+ expect(
+ sut.create({ type: operations.COMMAND_SHOW_ADDBOOKMARK, alter: true })
+ ).to.be.instanceOf(ShowAddBookmarkOperator);
+ expect(sut.create({ type: operations.FIND_START })).to.be.instanceOf(
+ StartFindOperator
+ );
+ expect(sut.create({ type: operations.CANCEL })).to.be.null;
+ });
+ });
+});
diff --git a/test/background/operators/impls/DuplicateTabOperator.test.ts b/test/background/operators/impls/DuplicateTabOperator.test.ts
new file mode 100644
index 0000000..ce2c19d
--- /dev/null
+++ b/test/background/operators/impls/DuplicateTabOperator.test.ts
@@ -0,0 +1,25 @@
+import { expect } from "chai";
+import DuplicateTabOperator from "../../../../src/background/operators/impls/DuplicateTabOperator";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+
+describe("DuplicateTabOperator", () => {
+ describe("#run", () => {
+ it("duplicate a tab", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: true });
+ await tabPresenter.create("https://example.com/3", { active: false });
+ const sut = new DuplicateTabOperator(tabPresenter);
+
+ await sut.run();
+
+ const tabs = await tabPresenter.getAll();
+ expect(tabs.map((t) => t.url)).to.deep.equal([
+ "https://example.com/1",
+ "https://example.com/2",
+ "https://example.com/3",
+ "https://example.com/2",
+ ]);
+ });
+ });
+});
diff --git a/test/background/operators/impls/InternalOpenURLOperator.test.ts b/test/background/operators/impls/InternalOpenURLOperator.test.ts
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/background/operators/impls/InternalOpenURLOperator.test.ts
diff --git a/test/background/operators/impls/InternalOperatorFactoryChain.test.ts b/test/background/operators/impls/InternalOperatorFactoryChain.test.ts
new file mode 100644
index 0000000..09029db
--- /dev/null
+++ b/test/background/operators/impls/InternalOperatorFactoryChain.test.ts
@@ -0,0 +1,37 @@
+import "reflect-metadata";
+import { expect } from "chai";
+import InternalOperatorFactoryChain from "../../../../src/background/operators/impls/InternalOperatorFactoryChain";
+import MockWindowPresenter from "../../mock/MockWindowPresenter";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+import MockConsoleClient from "../../mock/MockConsoleClient";
+import CancelOperator from "../../../../src/background/operators/impls/CancelOperator";
+import InternalOpenURLOperator from "../../../../src/background/operators/impls/InternalOpenURLOperator";
+import * as operations from "../../../../src/shared/operations";
+
+describe("InternalOperatorFactoryChain", () => {
+ describe("#create", () => {
+ it("returns a operator for the operation", async () => {
+ const windowPresenter = new MockWindowPresenter();
+ const tabPresenter = new MockTabPresenter();
+ const consoleClient = new MockConsoleClient();
+ const sut = new InternalOperatorFactoryChain(
+ windowPresenter,
+ tabPresenter,
+ consoleClient
+ );
+
+ expect(sut.create({ type: operations.CANCEL })).to.be.instanceOf(
+ CancelOperator
+ );
+ expect(
+ sut.create({
+ type: operations.INTERNAL_OPEN_URL,
+ url: "https://example.com",
+ newTab: false,
+ newWindow: false,
+ })
+ ).to.be.instanceOf(InternalOpenURLOperator);
+ expect(sut.create({ type: operations.COMMAND_SHOW })).to.be.null;
+ });
+ });
+});
diff --git a/test/background/operators/impls/NavigateHistoryNextOperator.test.ts b/test/background/operators/impls/NavigateHistoryNextOperator.test.ts
new file mode 100644
index 0000000..de8f597
--- /dev/null
+++ b/test/background/operators/impls/NavigateHistoryNextOperator.test.ts
@@ -0,0 +1,25 @@
+import sinon from "sinon";
+import NavigateHistoryNextOperator from "../../../../src/background/operators/impls/NavigateHistoryNextOperator";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+import MockNavigateClient from "../../mock/MockNavigateClient";
+
+describe("NavigateHistoryNextOperator", () => {
+ describe("#run", () => {
+ it("send a message to navigate next in the history", async () => {
+ const navigateClient = new MockNavigateClient();
+ const mock = sinon
+ .mock(navigateClient)
+ .expects("historyNext")
+ .withArgs(1);
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: true });
+ await tabPresenter.create("https://example.com/3", { active: false });
+ const sut = new NavigateHistoryNextOperator(tabPresenter, navigateClient);
+
+ await sut.run();
+
+ mock.verify();
+ });
+ });
+});
diff --git a/test/background/operators/impls/NavigateHistoryPrevOperator.test.ts b/test/background/operators/impls/NavigateHistoryPrevOperator.test.ts
new file mode 100644
index 0000000..6ebe71e
--- /dev/null
+++ b/test/background/operators/impls/NavigateHistoryPrevOperator.test.ts
@@ -0,0 +1,25 @@
+import sinon from "sinon";
+import NavigateHistoryPrevOperator from "../../../../src/background/operators/impls/NavigateHistoryPrevOperator";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+import MockNavigateClient from "../../mock/MockNavigateClient";
+
+describe("NavigateHistoryPrevOperator", () => {
+ describe("#run", () => {
+ it("send a message to navigate previous in the history", async () => {
+ const navigateClient = new MockNavigateClient();
+ const mock = sinon
+ .mock(navigateClient)
+ .expects("historyPrev")
+ .withArgs(1);
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: true });
+ await tabPresenter.create("https://example.com/3", { active: false });
+ const sut = new NavigateHistoryPrevOperator(tabPresenter, navigateClient);
+
+ await sut.run();
+
+ mock.verify();
+ });
+ });
+});
diff --git a/test/background/operators/impls/NavigateLinkNextOperator.test.ts b/test/background/operators/impls/NavigateLinkNextOperator.test.ts
new file mode 100644
index 0000000..09c4907
--- /dev/null
+++ b/test/background/operators/impls/NavigateLinkNextOperator.test.ts
@@ -0,0 +1,22 @@
+import sinon from "sinon";
+import NavigateLinkNextOperator from "../../../../src/background/operators/impls/NavigateLinkNextOperator";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+import MockNavigateClient from "../../mock/MockNavigateClient";
+
+describe("NavigateLinkNextOperator", () => {
+ describe("#run", () => {
+ it("send a message to navigate next page", async () => {
+ const navigateClient = new MockNavigateClient();
+ const mock = sinon.mock(navigateClient).expects("linkNext").withArgs(1);
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: true });
+ await tabPresenter.create("https://example.com/3", { active: false });
+ const sut = new NavigateLinkNextOperator(tabPresenter, navigateClient);
+
+ await sut.run();
+
+ mock.verify();
+ });
+ });
+});
diff --git a/test/background/operators/impls/NavigateLinkPrevOperator.test.ts b/test/background/operators/impls/NavigateLinkPrevOperator.test.ts
new file mode 100644
index 0000000..6b7f791
--- /dev/null
+++ b/test/background/operators/impls/NavigateLinkPrevOperator.test.ts
@@ -0,0 +1,22 @@
+import sinon from "sinon";
+import NavigateLinkPrevOperator from "../../../../src/background/operators/impls/NavigateLinkPrevOperator";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+import MockNavigateClient from "../../mock/MockNavigateClient";
+
+describe("NavigateLinkPrevOperator", () => {
+ describe("#run", () => {
+ it("send a message to navigate next page", async () => {
+ const navigateClient = new MockNavigateClient();
+ const mock = sinon.mock(navigateClient).expects("linkPrev").withArgs(1);
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: true });
+ await tabPresenter.create("https://example.com/3", { active: false });
+ const sut = new NavigateLinkPrevOperator(tabPresenter, navigateClient);
+
+ await sut.run();
+
+ mock.verify();
+ });
+ });
+});
diff --git a/test/background/operators/impls/NavigateOperatorFactoryChain.test.ts b/test/background/operators/impls/NavigateOperatorFactoryChain.test.ts
new file mode 100644
index 0000000..dfb5654
--- /dev/null
+++ b/test/background/operators/impls/NavigateOperatorFactoryChain.test.ts
@@ -0,0 +1,56 @@
+import "reflect-metadata";
+import { expect } from "chai";
+import NavigateOperatorFactoryChain from "../../../../src/background/operators/impls/NavigateOperatorFactoryChain";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+import MockNavigateClient from "../../mock/MockNavigateClient";
+import MockBrowserSettingRepository from "../../mock/MockBrowserSettingRepository";
+import NavigateHistoryPrevOperator from "../../../../src/background/operators/impls/NavigateHistoryPrevOperator";
+import NavigateHistoryNextOperator from "../../../../src/background/operators/impls/NavigateHistoryNextOperator";
+import NavigateLinkPrevOperator from "../../../../src/background/operators/impls/NavigateLinkPrevOperator";
+import NavigateLinkNextOperator from "../../../../src/background/operators/impls/NavigateLinkNextOperator";
+import NavigateParentOperator from "../../../../src/background/operators/impls/NavigateParentOperator";
+import NavigateRootOperator from "../../../../src/background/operators/impls/NavigateRootOperator";
+import OpenHomeOperator from "../../../../src/background/operators/impls/OpenHomeOperator";
+import OpenSourceOperator from "../../../../src/background/operators/impls/OpenSourceOperator";
+import * as operations from "../../../../src/shared/operations";
+
+describe("NavigateOperatorFactoryChain", () => {
+ describe("#create", () => {
+ it("returns a operator for the operation", async () => {
+ const tabPresenter = new MockTabPresenter();
+ const navigateClient = new MockNavigateClient();
+ const browserSettingRepository = new MockBrowserSettingRepository([]);
+ const sut = new NavigateOperatorFactoryChain(
+ tabPresenter,
+ navigateClient,
+ browserSettingRepository
+ );
+
+ expect(
+ sut.create({ type: operations.NAVIGATE_HISTORY_PREV })
+ ).to.be.instanceOf(NavigateHistoryPrevOperator);
+ expect(
+ sut.create({ type: operations.NAVIGATE_HISTORY_NEXT })
+ ).to.be.instanceOf(NavigateHistoryNextOperator);
+ expect(
+ sut.create({ type: operations.NAVIGATE_LINK_PREV })
+ ).to.be.instanceOf(NavigateLinkPrevOperator);
+ expect(
+ sut.create({ type: operations.NAVIGATE_LINK_NEXT })
+ ).to.be.instanceOf(NavigateLinkNextOperator);
+ expect(sut.create({ type: operations.NAVIGATE_PARENT })).to.be.instanceOf(
+ NavigateParentOperator
+ );
+ expect(sut.create({ type: operations.NAVIGATE_ROOT })).to.be.instanceOf(
+ NavigateRootOperator
+ );
+ expect(sut.create({ type: operations.PAGE_SOURCE })).to.be.instanceOf(
+ OpenSourceOperator
+ );
+ expect(
+ sut.create({ type: operations.PAGE_HOME, newTab: false })
+ ).to.be.instanceOf(OpenHomeOperator);
+ expect(sut.create({ type: operations.CANCEL })).to.be.null;
+ });
+ });
+});
diff --git a/test/background/operators/impls/NavigateParentOperator.test.ts b/test/background/operators/impls/NavigateParentOperator.test.ts
new file mode 100644
index 0000000..cc57f17
--- /dev/null
+++ b/test/background/operators/impls/NavigateParentOperator.test.ts
@@ -0,0 +1,53 @@
+import { expect } from "chai";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+import NavigateParentOperator from "../../../../src/background/operators/impls/NavigateParentOperator";
+
+describe("NavigateParentOperator", () => {
+ describe("#run", () => {
+ it("opens a parent directory of the file in the URL", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/fruits/yellow/banana", {
+ active: true,
+ });
+ const sut = new NavigateParentOperator(tabPresenter);
+
+ await sut.run();
+
+ const url = (await tabPresenter.getCurrent()).url;
+ expect(url).to.be.equal("https://example.com/fruits/yellow/");
+ });
+
+ it("opens a parent directory of the directoryin the URL", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/fruits/yellow/");
+ const sut = new NavigateParentOperator(tabPresenter);
+
+ await sut.run();
+
+ const url = (await tabPresenter.getCurrent()).url;
+ expect(url).to.be.equal("https://example.com/fruits/");
+ });
+
+ it("removes a hash in the URL", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/fruits/yellow/#top");
+ const sut = new NavigateParentOperator(tabPresenter);
+
+ await sut.run();
+
+ const url = (await tabPresenter.getCurrent()).url;
+ expect(url).to.be.equal("https://example.com/fruits/yellow/");
+ });
+
+ it("removes query parameters in the URL", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/search?q=apple");
+ const sut = new NavigateParentOperator(tabPresenter);
+
+ await sut.run();
+
+ const url = (await tabPresenter.getCurrent()).url;
+ expect(url).to.be.equal("https://example.com/search");
+ });
+ });
+});
diff --git a/test/background/operators/impls/NavigateRootOperator.test.ts b/test/background/operators/impls/NavigateRootOperator.test.ts
new file mode 100644
index 0000000..bbe574c
--- /dev/null
+++ b/test/background/operators/impls/NavigateRootOperator.test.ts
@@ -0,0 +1,18 @@
+import { expect } from "chai";
+import NavigateRootOperator from "../../../../src/background/operators/impls/NavigateRootOperator";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+
+describe("NavigateRootOperator", () => {
+ describe("#run", () => {
+ it("opens root directory in the URL", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/search?q=apple#top");
+ const sut = new NavigateRootOperator(tabPresenter);
+
+ await sut.run();
+
+ const url = (await tabPresenter.getCurrent()).url;
+ expect(url).to.be.equal("https://example.com");
+ });
+ });
+});
diff --git a/test/background/operators/impls/OpenHomeOperator.test.ts b/test/background/operators/impls/OpenHomeOperator.test.ts
new file mode 100644
index 0000000..3c9288f
--- /dev/null
+++ b/test/background/operators/impls/OpenHomeOperator.test.ts
@@ -0,0 +1,70 @@
+import { expect } from "chai";
+import OpenHomeOperator from "../../../../src/background/operators/impls/OpenHomeOperator";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+import MockBrowserSettingRepository from "../../mock/MockBrowserSettingRepository";
+
+describe("OpenHomeOperator", () => {
+ describe("#run", () => {
+ it("opens a home page of the browser into the current tab", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/");
+ const browserSettingRepository = new MockBrowserSettingRepository([
+ "https://example.net/",
+ ]);
+ const sut = new OpenHomeOperator(
+ tabPresenter,
+ browserSettingRepository,
+ false
+ );
+
+ await sut.run();
+
+ const url = (await tabPresenter.getCurrent()).url;
+ expect(url).to.be.equal("https://example.net/");
+ });
+
+ it("opens a home page of the browser into a new tab", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/");
+ const browserSettingRepository = new MockBrowserSettingRepository([
+ "https://example.net/",
+ ]);
+ const sut = new OpenHomeOperator(
+ tabPresenter,
+ browserSettingRepository,
+ true
+ );
+
+ await sut.run();
+
+ const urls = (await tabPresenter.getAll()).map((t) => t.url);
+ expect(urls).to.be.deep.equal([
+ "https://example.com/",
+ "https://example.net/",
+ ]);
+ });
+
+ it("opens home pages of the browser", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/");
+ const browserSettingRepository = new MockBrowserSettingRepository([
+ "https://example.net/",
+ "https://example.org/",
+ ]);
+ const sut = new OpenHomeOperator(
+ tabPresenter,
+ browserSettingRepository,
+ false
+ );
+
+ await sut.run();
+
+ const urls = (await tabPresenter.getAll()).map((t) => t.url);
+ expect(urls).to.be.deep.equal([
+ "https://example.com/",
+ "https://example.net/",
+ "https://example.org/",
+ ]);
+ });
+ });
+});
diff --git a/test/background/operators/impls/OpenSourceOperator.test.ts b/test/background/operators/impls/OpenSourceOperator.test.ts
new file mode 100644
index 0000000..541032b
--- /dev/null
+++ b/test/background/operators/impls/OpenSourceOperator.test.ts
@@ -0,0 +1,21 @@
+import { expect } from "chai";
+import OpenSourceOperator from "../../../../src/background/operators/impls/OpenSourceOperator";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+
+describe("OpenSourceOperator", () => {
+ describe("#run", () => {
+ it("opens view-source URL of the current tab", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/");
+ const sut = new OpenSourceOperator(tabPresenter);
+
+ await sut.run();
+
+ const urls = (await tabPresenter.getAll()).map((t) => t.url);
+ expect(urls).to.be.deep.equal([
+ "https://example.com/",
+ "view-source:https://example.com/",
+ ]);
+ });
+ });
+});
diff --git a/test/background/operators/impls/PinTabOperator.test.ts b/test/background/operators/impls/PinTabOperator.test.ts
new file mode 100644
index 0000000..0c940b6
--- /dev/null
+++ b/test/background/operators/impls/PinTabOperator.test.ts
@@ -0,0 +1,25 @@
+import { expect } from "chai";
+import PinTabOperator from "../../../../src/background/operators/impls/PinTabOperator";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+
+describe("PinTabOperator", () => {
+ describe("#run", () => {
+ it("make pinned to the current tab", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/", {
+ active: true,
+ pinned: false,
+ });
+ await tabPresenter.create("https://example.com/", {
+ active: false,
+ pinned: false,
+ });
+ const sut = new PinTabOperator(tabPresenter);
+
+ await sut.run();
+
+ const pins = (await tabPresenter.getAll()).map((t) => t.pinned);
+ expect(pins).to.deep.equal([true, false]);
+ });
+ });
+});
diff --git a/test/background/operators/impls/ReloadTabOperator.test.ts b/test/background/operators/impls/ReloadTabOperator.test.ts
new file mode 100644
index 0000000..e87782b
--- /dev/null
+++ b/test/background/operators/impls/ReloadTabOperator.test.ts
@@ -0,0 +1,34 @@
+import sinon from "sinon";
+import ReloadTabOperator from "../../../../src/background/operators/impls/ReloadTabOperator";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+
+describe("ReloadTabOperator", () => {
+ describe("#run", () => {
+ it("reloads the current tab with cache", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/", { active: true });
+ await tabPresenter.create("https://example.com/", { active: false });
+ const mock = sinon.mock(tabPresenter).expects("reload").withArgs(0, true);
+
+ const sut = new ReloadTabOperator(tabPresenter, true);
+ await sut.run();
+
+ mock.verify();
+ });
+
+ it("reloads the current tab without cache", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/", { active: true });
+ await tabPresenter.create("https://example.com/", { active: false });
+ const mock = sinon
+ .mock(tabPresenter)
+ .expects("reload")
+ .withArgs(0, false);
+
+ const sut = new ReloadTabOperator(tabPresenter, false);
+ await sut.run();
+
+ mock.verify();
+ });
+ });
+});
diff --git a/test/background/operators/impls/ReopenTabOperator.test.ts b/test/background/operators/impls/ReopenTabOperator.test.ts
new file mode 100644
index 0000000..43b1575
--- /dev/null
+++ b/test/background/operators/impls/ReopenTabOperator.test.ts
@@ -0,0 +1,17 @@
+import sinon from "sinon";
+import ReopenTabOperator from "../../../../src/background/operators/impls/ReopenTabOperator";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+
+describe("ReopenTabOperator", () => {
+ describe("#run", () => {
+ it("reopens closed tabs", async () => {
+ const tabPresenter = new MockTabPresenter();
+ const mock = sinon.mock(tabPresenter).expects("reopen");
+
+ const sut = new ReopenTabOperator(tabPresenter);
+ await sut.run();
+
+ mock.verify();
+ });
+ });
+});
diff --git a/test/background/operators/impls/RepeatLastOperator.test.ts b/test/background/operators/impls/RepeatLastOperator.test.ts
new file mode 100644
index 0000000..57f1227
--- /dev/null
+++ b/test/background/operators/impls/RepeatLastOperator.test.ts
@@ -0,0 +1,56 @@
+import RepeatLastOperator from "../../../../src/background/operators/impls/RepeatLastOperator";
+import MockRepeatRepository from "../../mock/MockRepeatRepository";
+import OperatorFactory from "../../../../src/background/operators/OperatorFactory";
+import * as operations from "../../../../src/shared/operations";
+import Operator from "../../../../src/background/operators/Operator";
+import sinon from "sinon";
+
+class MockOperatorFactory implements OperatorFactory {
+ create(_op: operations.Operation): Operator {
+ throw new Error("not implemented");
+ }
+}
+
+class MockOperator implements Operator {
+ run(): Promise<void> {
+ throw new Error("not implemented");
+ }
+}
+
+describe("RepeatLastOperator", () => {
+ describe("#run", () => {
+ it("repeat last operation", async () => {
+ const operator = new MockOperator();
+ const operatorMock = sinon.mock(operator).expects("run").once();
+ const repeatRepository = new MockRepeatRepository();
+ repeatRepository.setLastOperation({ type: operations.CANCEL });
+
+ const operatorFactory = new MockOperatorFactory();
+ const operatorFactoryMock = sinon
+ .mock(operatorFactory)
+ .expects("create")
+ .withArgs({ type: operations.CANCEL });
+ operatorFactoryMock.returns(operator);
+
+ const sut = new RepeatLastOperator(repeatRepository, operatorFactory);
+ await sut.run();
+
+ operatorFactoryMock.verify();
+ operatorMock.verify();
+ });
+
+ it("does nothing if no last operations", async () => {
+ const repeatRepository = new MockRepeatRepository();
+ const operatorFactory = new MockOperatorFactory();
+ const operatorFactoryMock = sinon
+ .mock(operatorFactory)
+ .expects("create")
+ .never();
+
+ const sut = new RepeatLastOperator(repeatRepository, operatorFactory);
+ await sut.run();
+
+ operatorFactoryMock.verify();
+ });
+ });
+});
diff --git a/test/background/operators/impls/RepeatOperatorFactoryChain.test.ts b/test/background/operators/impls/RepeatOperatorFactoryChain.test.ts
new file mode 100644
index 0000000..e12d788
--- /dev/null
+++ b/test/background/operators/impls/RepeatOperatorFactoryChain.test.ts
@@ -0,0 +1,32 @@
+import "reflect-metadata";
+import { expect } from "chai";
+import RepeatOperatorFactoryChain from "../../../../src/background/operators/impls/RepeatOperatorFactoryChain";
+import RepeatLastOperator from "../../../../src/background/operators/impls/RepeatLastOperator";
+import OperatorFactory from "../../../../src/background/operators/OperatorFactory";
+import MockRepeatRepository from "../../mock/MockRepeatRepository";
+import Operator from "../../../../src/content/operators/Operator";
+import * as operations from "../../../../src/shared/operations";
+
+class MockOperatorFactory implements OperatorFactory {
+ create(_op: operations.Operation): Operator {
+ throw new Error("not implemented");
+ }
+}
+
+describe("RepeatOperatorFactoryChain", () => {
+ describe("#create", () => {
+ it("returns a operator for the operation", async () => {
+ const repeatRepository = new MockRepeatRepository();
+ const operatorFactory = new MockOperatorFactory();
+ const sut = new RepeatOperatorFactoryChain(
+ repeatRepository,
+ operatorFactory
+ );
+
+ expect(sut.create({ type: operations.REPEAT_LAST })).to.be.instanceOf(
+ RepeatLastOperator
+ );
+ expect(sut.create({ type: operations.CANCEL })).to.be.null;
+ });
+ });
+});
diff --git a/test/background/operators/impls/ResetZoomOperator.test.ts b/test/background/operators/impls/ResetZoomOperator.test.ts
new file mode 100644
index 0000000..68cda05
--- /dev/null
+++ b/test/background/operators/impls/ResetZoomOperator.test.ts
@@ -0,0 +1,17 @@
+import sinon from "sinon";
+import ResetZoomOperator from "../../../../src/background/operators/impls/ResetZoomOperator";
+import MockZoomPresenter from "../../mock/MockZoomPresenter";
+
+describe("ResetZoomOperator", () => {
+ describe("#run", () => {
+ it("resets zoom on the tab", async () => {
+ const zoomPresenter = new MockZoomPresenter();
+ const mock = sinon.mock(zoomPresenter).expects("resetZoom").once();
+
+ const sut = new ResetZoomOperator(zoomPresenter);
+ await sut.run();
+
+ mock.verify();
+ });
+ });
+});
diff --git a/test/background/operators/impls/SelectFirstTabOperator.test.ts b/test/background/operators/impls/SelectFirstTabOperator.test.ts
new file mode 100644
index 0000000..a3f1d7e
--- /dev/null
+++ b/test/background/operators/impls/SelectFirstTabOperator.test.ts
@@ -0,0 +1,20 @@
+import { expect } from "chai";
+import SelectFirstTabOperator from "../../../../src/background/operators/impls/SelectFirstTabOperator";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+
+describe("SelectFirstTabOperator", () => {
+ describe("#run", () => {
+ it("select the leftmost tab", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: true });
+ await tabPresenter.create("https://example.com/3", { active: false });
+
+ const sut = new SelectFirstTabOperator(tabPresenter);
+ await sut.run();
+
+ const url = (await tabPresenter.getCurrent()).url;
+ expect(url).to.equal("https://example.com/1");
+ });
+ });
+});
diff --git a/test/background/operators/impls/SelectLastTabOperator.test.ts b/test/background/operators/impls/SelectLastTabOperator.test.ts
new file mode 100644
index 0000000..b8cf5c4
--- /dev/null
+++ b/test/background/operators/impls/SelectLastTabOperator.test.ts
@@ -0,0 +1,20 @@
+import { expect } from "chai";
+import SelectLastTabOperator from "../../../../src/background/operators/impls/SelectLastTabOperator";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+
+describe("SelectLastTabOperator", () => {
+ describe("#run", () => {
+ it("select the rightmost tab", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: true });
+ await tabPresenter.create("https://example.com/3", { active: false });
+
+ const sut = new SelectLastTabOperator(tabPresenter);
+ await sut.run();
+
+ const url = (await tabPresenter.getCurrent()).url;
+ expect(url).to.equal("https://example.com/3");
+ });
+ });
+});
diff --git a/test/background/operators/impls/SelectPreviousSelectedTabOperator.test.ts b/test/background/operators/impls/SelectPreviousSelectedTabOperator.test.ts
new file mode 100644
index 0000000..5e6cc73
--- /dev/null
+++ b/test/background/operators/impls/SelectPreviousSelectedTabOperator.test.ts
@@ -0,0 +1,38 @@
+import { expect } from "chai";
+import sinon from "sinon";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+import SelectPreviousSelectedTabOperator from "../../../../src/background/operators/impls/SelectPreviousSelectedTabOperator";
+
+describe("SelectPreviousSelectedTabOperator", () => {
+ describe("#run", () => {
+ it("select the last-selected tab", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: true });
+ await tabPresenter.create("https://example.com/3", { active: false });
+ sinon.stub(tabPresenter, "getLastSelectedId").returns(Promise.resolve(0));
+
+ const sut = new SelectPreviousSelectedTabOperator(tabPresenter);
+ await sut.run();
+
+ const url = (await tabPresenter.getCurrent()).url;
+ expect(url).to.equal("https://example.com/1");
+ });
+
+ it("do nothing if no last-selected tabs", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: true });
+ await tabPresenter.create("https://example.com/3", { active: false });
+ sinon
+ .stub(tabPresenter, "getLastSelectedId")
+ .returns(Promise.resolve(undefined));
+ const mock = sinon.mock(tabPresenter).expects("select").never();
+
+ const sut = new SelectPreviousSelectedTabOperator(tabPresenter);
+ await sut.run();
+
+ mock.verify();
+ });
+ });
+});
diff --git a/test/background/operators/impls/SelectTabNextOperator.test.ts b/test/background/operators/impls/SelectTabNextOperator.test.ts
new file mode 100644
index 0000000..5952d92
--- /dev/null
+++ b/test/background/operators/impls/SelectTabNextOperator.test.ts
@@ -0,0 +1,35 @@
+import { expect } from "chai";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+import SelectTabNextOperator from "../../../../src/background/operators/impls/SelectTabNextOperator";
+
+describe("SelectTabNextOperator", () => {
+ describe("#run", () => {
+ it("select a right tab of the current tab", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: true });
+ await tabPresenter.create("https://example.com/3", { active: false });
+
+ const sut = new SelectTabNextOperator(tabPresenter);
+ await sut.run();
+
+ const url = (await tabPresenter.getCurrent()).url;
+ expect(url).to.equal("https://example.com/3");
+ });
+ });
+
+ describe("#run", () => {
+ it("select a right tab of the current tab in rotation", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: false });
+ await tabPresenter.create("https://example.com/3", { active: true });
+
+ const sut = new SelectTabNextOperator(tabPresenter);
+ await sut.run();
+
+ const url = (await tabPresenter.getCurrent()).url;
+ expect(url).to.equal("https://example.com/1");
+ });
+ });
+});
diff --git a/test/background/operators/impls/SelectTabPrevOperator.test.ts b/test/background/operators/impls/SelectTabPrevOperator.test.ts
new file mode 100644
index 0000000..c9092fa
--- /dev/null
+++ b/test/background/operators/impls/SelectTabPrevOperator.test.ts
@@ -0,0 +1,35 @@
+import { expect } from "chai";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+import SelectTabPrevOperator from "../../../../src/background/operators/impls/SelectTabPrevOperator";
+
+describe("SelectTabPrevOperator", () => {
+ describe("#run", () => {
+ it("select a left tab of the current tab", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: true });
+ await tabPresenter.create("https://example.com/3", { active: false });
+
+ const sut = new SelectTabPrevOperator(tabPresenter);
+ await sut.run();
+
+ const url = (await tabPresenter.getCurrent()).url;
+ expect(url).to.equal("https://example.com/1");
+ });
+ });
+
+ describe("#run", () => {
+ it("select a left tab of the current tab in rotation", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: true });
+ await tabPresenter.create("https://example.com/2", { active: false });
+ await tabPresenter.create("https://example.com/3", { active: false });
+
+ const sut = new SelectTabPrevOperator(tabPresenter);
+ await sut.run();
+
+ const url = (await tabPresenter.getCurrent()).url;
+ expect(url).to.equal("https://example.com/3");
+ });
+ });
+});
diff --git a/test/background/operators/impls/ShowAddBookmarkOperator.test.ts b/test/background/operators/impls/ShowAddBookmarkOperator.test.ts
new file mode 100644
index 0000000..1e083c2
--- /dev/null
+++ b/test/background/operators/impls/ShowAddBookmarkOperator.test.ts
@@ -0,0 +1,50 @@
+import sinon from "sinon";
+import ShowAddBookmarkOperator from "../../../../src/background/operators/impls/ShowAddBookmarkOperator";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+import MockConsoleClient from "../../mock/MockConsoleClient";
+
+describe("ShowAddBookmarkOperator", () => {
+ describe("#run", () => {
+ it("show command with addbookmark command", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: true });
+ await tabPresenter.create("https://example.com/3", { active: false });
+ const consoleClient = new MockConsoleClient();
+ const mock = sinon
+ .mock(consoleClient)
+ .expects("showCommand")
+ .withArgs(1, "addbookmark ");
+
+ const sut = new ShowAddBookmarkOperator(
+ tabPresenter,
+ consoleClient,
+ false
+ );
+ await sut.run();
+
+ mock.verify();
+ });
+
+ it("show command with addbookmark command and an URL of the current tab", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: true });
+ await tabPresenter.create("https://example.com/3", { active: false });
+ const consoleClient = new MockConsoleClient();
+ const mock = sinon
+ .mock(consoleClient)
+ .expects("showCommand")
+ .withArgs(1, "addbookmark welcome, world");
+
+ const sut = new ShowAddBookmarkOperator(
+ tabPresenter,
+ consoleClient,
+ true
+ );
+ await sut.run();
+
+ mock.verify();
+ });
+ });
+});
diff --git a/test/background/operators/impls/ShowBufferCommandOperator.test.ts b/test/background/operators/impls/ShowBufferCommandOperator.test.ts
new file mode 100644
index 0000000..91455b3
--- /dev/null
+++ b/test/background/operators/impls/ShowBufferCommandOperator.test.ts
@@ -0,0 +1,25 @@
+import sinon from "sinon";
+import ShowBufferCommandOperator from "../../../../src/background/operators/impls/ShowBufferCommandOperator";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+import MockConsoleClient from "../../mock/MockConsoleClient";
+
+describe("ShowBufferCommandOperator", () => {
+ describe("#run", () => {
+ it("show command with buffer command", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: true });
+ await tabPresenter.create("https://example.com/3", { active: false });
+ const consoleClient = new MockConsoleClient();
+ const mock = sinon
+ .mock(consoleClient)
+ .expects("showCommand")
+ .withArgs(1, "buffer ");
+
+ const sut = new ShowBufferCommandOperator(tabPresenter, consoleClient);
+ await sut.run();
+
+ mock.verify();
+ });
+ });
+});
diff --git a/test/background/operators/impls/ShowCommandOperator.test.ts b/test/background/operators/impls/ShowCommandOperator.test.ts
new file mode 100644
index 0000000..83b028c
--- /dev/null
+++ b/test/background/operators/impls/ShowCommandOperator.test.ts
@@ -0,0 +1,25 @@
+import sinon from "sinon";
+import ShowCommandOperator from "../../../../src/background/operators/impls/ShowCommandOperator";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+import MockConsoleClient from "../../mock/MockConsoleClient";
+
+describe("ShowCommandOperator", () => {
+ describe("#run", () => {
+ it("show command with addbookmark command", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: true });
+ await tabPresenter.create("https://example.com/3", { active: false });
+ const consoleClient = new MockConsoleClient();
+ const mock = sinon
+ .mock(consoleClient)
+ .expects("showCommand")
+ .withArgs(1, "");
+
+ const sut = new ShowCommandOperator(tabPresenter, consoleClient);
+ await sut.run();
+
+ mock.verify();
+ });
+ });
+});
diff --git a/test/background/operators/impls/ShowOpenCommandOperator.test.ts b/test/background/operators/impls/ShowOpenCommandOperator.test.ts
new file mode 100644
index 0000000..2c2105a
--- /dev/null
+++ b/test/background/operators/impls/ShowOpenCommandOperator.test.ts
@@ -0,0 +1,50 @@
+import sinon from "sinon";
+import ShowOpenCommandOperator from "../../../../src/background/operators/impls/ShowOpenCommandOperator";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+import MockConsoleClient from "../../mock/MockConsoleClient";
+
+describe("ShowOpenCommandOperator", () => {
+ describe("#run", () => {
+ it("show command with open command", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: true });
+ await tabPresenter.create("https://example.com/3", { active: false });
+ const consoleClient = new MockConsoleClient();
+ const mock = sinon
+ .mock(consoleClient)
+ .expects("showCommand")
+ .withArgs(1, "open ");
+
+ const sut = new ShowOpenCommandOperator(
+ tabPresenter,
+ consoleClient,
+ false
+ );
+ await sut.run();
+
+ mock.verify();
+ });
+
+ it("show command with open command and an URL of the current tab", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: true });
+ await tabPresenter.create("https://example.com/3", { active: false });
+ const consoleClient = new MockConsoleClient();
+ const mock = sinon
+ .mock(consoleClient)
+ .expects("showCommand")
+ .withArgs(1, "open https://example.com/2");
+
+ const sut = new ShowOpenCommandOperator(
+ tabPresenter,
+ consoleClient,
+ true
+ );
+ await sut.run();
+
+ mock.verify();
+ });
+ });
+});
diff --git a/test/background/operators/impls/ShowTabOpenCommandOperator.test.ts b/test/background/operators/impls/ShowTabOpenCommandOperator.test.ts
new file mode 100644
index 0000000..e291d05
--- /dev/null
+++ b/test/background/operators/impls/ShowTabOpenCommandOperator.test.ts
@@ -0,0 +1,50 @@
+import sinon from "sinon";
+import ShowTabOpenCommandOperator from "../../../../src/background/operators/impls/ShowTabOpenCommandOperator";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+import MockConsoleClient from "../../mock/MockConsoleClient";
+
+describe("ShowTabOpenCommandOperator", () => {
+ describe("#run", () => {
+ it("show command with tabopen command", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: true });
+ await tabPresenter.create("https://example.com/3", { active: false });
+ const consoleClient = new MockConsoleClient();
+ const mock = sinon
+ .mock(consoleClient)
+ .expects("showCommand")
+ .withArgs(1, "tabopen ");
+
+ const sut = new ShowTabOpenCommandOperator(
+ tabPresenter,
+ consoleClient,
+ false
+ );
+ await sut.run();
+
+ mock.verify();
+ });
+
+ it("show command with tabopen command and an URL of the current tab", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: true });
+ await tabPresenter.create("https://example.com/3", { active: false });
+ const consoleClient = new MockConsoleClient();
+ const mock = sinon
+ .mock(consoleClient)
+ .expects("showCommand")
+ .withArgs(1, "tabopen https://example.com/2");
+
+ const sut = new ShowTabOpenCommandOperator(
+ tabPresenter,
+ consoleClient,
+ true
+ );
+ await sut.run();
+
+ mock.verify();
+ });
+ });
+});
diff --git a/test/background/operators/impls/ShowWinOpenCommandOperator.test.ts b/test/background/operators/impls/ShowWinOpenCommandOperator.test.ts
new file mode 100644
index 0000000..c81a2d4
--- /dev/null
+++ b/test/background/operators/impls/ShowWinOpenCommandOperator.test.ts
@@ -0,0 +1,50 @@
+import sinon from "sinon";
+import ShowWinOpenCommandOperator from "../../../../src/background/operators/impls/ShowWinOpenCommandOperator";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+import MockConsoleClient from "../../mock/MockConsoleClient";
+
+describe("ShowWinOpenCommandOperator", () => {
+ describe("#run", () => {
+ it("show command with winopen command", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: true });
+ await tabPresenter.create("https://example.com/3", { active: false });
+ const consoleClient = new MockConsoleClient();
+ const mock = sinon
+ .mock(consoleClient)
+ .expects("showCommand")
+ .withArgs(1, "winopen ");
+
+ const sut = new ShowWinOpenCommandOperator(
+ tabPresenter,
+ consoleClient,
+ false
+ );
+ await sut.run();
+
+ mock.verify();
+ });
+
+ it("show command with winopen command and an URL of the current tab", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: true });
+ await tabPresenter.create("https://example.com/3", { active: false });
+ const consoleClient = new MockConsoleClient();
+ const mock = sinon
+ .mock(consoleClient)
+ .expects("showCommand")
+ .withArgs(1, "winopen https://example.com/2");
+
+ const sut = new ShowWinOpenCommandOperator(
+ tabPresenter,
+ consoleClient,
+ true
+ );
+ await sut.run();
+
+ mock.verify();
+ });
+ });
+});
diff --git a/test/background/operators/impls/StartFindOperator.test.ts b/test/background/operators/impls/StartFindOperator.test.ts
new file mode 100644
index 0000000..7764520
--- /dev/null
+++ b/test/background/operators/impls/StartFindOperator.test.ts
@@ -0,0 +1,22 @@
+import sinon from "sinon";
+import StartFindOperator from "../../../../src/background/operators/impls/StartFindOperator";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+import MockConsoleClient from "../../mock/MockConsoleClient";
+
+describe("StartFindOperator", () => {
+ describe("#run", () => {
+ it("show find console", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/1", { active: false });
+ await tabPresenter.create("https://example.com/2", { active: true });
+ await tabPresenter.create("https://example.com/3", { active: false });
+ const consoleClient = new MockConsoleClient();
+ const mock = sinon.mock(consoleClient).expects("showFind").withArgs(1);
+
+ const sut = new StartFindOperator(tabPresenter, consoleClient);
+ await sut.run();
+
+ mock.verify();
+ });
+ });
+});
diff --git a/test/background/operators/impls/TabOperatorFactoryChain.test.ts b/test/background/operators/impls/TabOperatorFactoryChain.test.ts
new file mode 100644
index 0000000..7ab5de0
--- /dev/null
+++ b/test/background/operators/impls/TabOperatorFactoryChain.test.ts
@@ -0,0 +1,71 @@
+import "reflect-metadata";
+import { expect } from "chai";
+import TabOperatorFactoryChain from "../../../../src/background/operators/impls/TabOperatorFactoryChain";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+import DuplicateTabOperator from "../../../../src/background/operators/impls/DuplicateTabOperator";
+import TogglePinnedTabOperator from "../../../../src/background/operators/impls/TogglePinnedTabOperator";
+import UnpinTabOperator from "../../../../src/background/operators/impls/UnpinTabOperator";
+import PinTabOperator from "../../../../src/background/operators/impls/PinTabOperator";
+import ReloadTabOperator from "../../../../src/background/operators/impls/ReloadTabOperator";
+import SelectPreviousSelectedTabOperator from "../../../../src/background/operators/impls/SelectPreviousSelectedTabOperator";
+import SelectLastTabOperator from "../../../../src/background/operators/impls/SelectLastTabOperator";
+import SelectFirstTabOperator from "../../../../src/background/operators/impls/SelectFirstTabOperator";
+import SelectTabNextOperator from "../../../../src/background/operators/impls/SelectTabNextOperator";
+import SelectTabPrevOperator from "../../../../src/background/operators/impls/SelectTabPrevOperator";
+import ReopenTabOperator from "../../../../src/background/operators/impls/ReopenTabOperator";
+import CloseTabOperator from "../../../../src/background/operators/impls/CloseTabOperator";
+import CloseTabRightOperator from "../../../../src/background/operators/impls/CloseTabRightOperator";
+import * as operations from "../../../../src/shared/operations";
+
+describe("TabOperatorFactoryChain", () => {
+ describe("#create", () => {
+ it("returns a operator for the operation", async () => {
+ const tabPresenter = new MockTabPresenter();
+ const sut = new TabOperatorFactoryChain(tabPresenter);
+
+ expect(sut.create({ type: operations.TAB_CLOSE })).to.be.instanceOf(
+ CloseTabOperator
+ );
+ expect(sut.create({ type: operations.TAB_CLOSE_RIGHT })).to.be.instanceOf(
+ CloseTabRightOperator
+ );
+ expect(sut.create({ type: operations.TAB_CLOSE_FORCE })).to.be.instanceOf(
+ CloseTabOperator
+ );
+ expect(sut.create({ type: operations.TAB_REOPEN })).to.be.instanceOf(
+ ReopenTabOperator
+ );
+ expect(sut.create({ type: operations.TAB_PREV })).to.be.instanceOf(
+ SelectTabPrevOperator
+ );
+ expect(sut.create({ type: operations.TAB_NEXT })).to.be.instanceOf(
+ SelectTabNextOperator
+ );
+ expect(sut.create({ type: operations.TAB_FIRST })).to.be.instanceOf(
+ SelectFirstTabOperator
+ );
+ expect(
+ sut.create({ type: operations.TAB_LAST, newTab: false })
+ ).to.be.instanceOf(SelectLastTabOperator);
+ expect(
+ sut.create({ type: operations.TAB_PREV_SEL, newTab: false })
+ ).to.be.instanceOf(SelectPreviousSelectedTabOperator);
+ expect(
+ sut.create({ type: operations.TAB_RELOAD, cache: false })
+ ).to.be.instanceOf(ReloadTabOperator);
+ expect(sut.create({ type: operations.TAB_PIN })).to.be.instanceOf(
+ PinTabOperator
+ );
+ expect(sut.create({ type: operations.TAB_UNPIN })).to.be.instanceOf(
+ UnpinTabOperator
+ );
+ expect(
+ sut.create({ type: operations.TAB_TOGGLE_PINNED })
+ ).to.be.instanceOf(TogglePinnedTabOperator);
+ expect(sut.create({ type: operations.TAB_DUPLICATE })).to.be.instanceOf(
+ DuplicateTabOperator
+ );
+ expect(sut.create({ type: operations.CANCEL })).to.be.null;
+ });
+ });
+});
diff --git a/test/background/operators/impls/TogglePinnedTabOperator.test.ts b/test/background/operators/impls/TogglePinnedTabOperator.test.ts
new file mode 100644
index 0000000..f155f83
--- /dev/null
+++ b/test/background/operators/impls/TogglePinnedTabOperator.test.ts
@@ -0,0 +1,32 @@
+import { expect } from "chai";
+import TogglePinnedTabOperator from "../../../../src/background/operators/impls/TogglePinnedTabOperator";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+
+describe("TogglePinnedTabOperator", () => {
+ describe("#run", () => {
+ it("toggle pinned to the current tab", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/", {
+ active: true,
+ pinned: false,
+ });
+ await tabPresenter.create("https://example.com/", {
+ active: false,
+ pinned: false,
+ });
+ const sut = new TogglePinnedTabOperator(tabPresenter);
+
+ await sut.run();
+ expect((await tabPresenter.getAll()).map((t) => t.pinned)).to.deep.equal([
+ true,
+ false,
+ ]);
+
+ await sut.run();
+ expect((await tabPresenter.getAll()).map((t) => t.pinned)).to.deep.equal([
+ false,
+ false,
+ ]);
+ });
+ });
+});
diff --git a/test/background/operators/impls/UnpinTabOperator.test.ts b/test/background/operators/impls/UnpinTabOperator.test.ts
new file mode 100644
index 0000000..745f48c
--- /dev/null
+++ b/test/background/operators/impls/UnpinTabOperator.test.ts
@@ -0,0 +1,25 @@
+import { expect } from "chai";
+import UnpinTabOperator from "../../../../src/background/operators/impls/UnpinTabOperator";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+
+describe("UnpinTabOperator", () => {
+ describe("#run", () => {
+ it("make unpinned to the current tab", async () => {
+ const tabPresenter = new MockTabPresenter();
+ await tabPresenter.create("https://example.com/", {
+ active: true,
+ pinned: true,
+ });
+ await tabPresenter.create("https://example.com/", {
+ active: false,
+ pinned: true,
+ });
+ const sut = new UnpinTabOperator(tabPresenter);
+
+ await sut.run();
+
+ const pins = (await tabPresenter.getAll()).map((t) => t.pinned);
+ expect(pins).to.deep.equal([false, true]);
+ });
+ });
+});
diff --git a/test/background/operators/impls/ZoomInOperator.test.ts b/test/background/operators/impls/ZoomInOperator.test.ts
new file mode 100644
index 0000000..097e760
--- /dev/null
+++ b/test/background/operators/impls/ZoomInOperator.test.ts
@@ -0,0 +1,17 @@
+import sinon from "sinon";
+import ZoomInOperator from "../../../../src/background/operators/impls/ZoomInOperator";
+import MockZoomPresenter from "../../mock/MockZoomPresenter";
+
+describe("ZoomInOperator", () => {
+ describe("#run", () => {
+ it("zoom-out the current tab", async () => {
+ const zoomPresenter = new MockZoomPresenter();
+ const mock = sinon.mock(zoomPresenter).expects("zoomIn").once();
+
+ const sut = new ZoomInOperator(zoomPresenter);
+ await sut.run();
+
+ mock.verify();
+ });
+ });
+});
diff --git a/test/background/operators/impls/ZoomOperatorFactoryChain.test.ts b/test/background/operators/impls/ZoomOperatorFactoryChain.test.ts
new file mode 100644
index 0000000..10c1cee
--- /dev/null
+++ b/test/background/operators/impls/ZoomOperatorFactoryChain.test.ts
@@ -0,0 +1,28 @@
+import "reflect-metadata";
+import { expect } from "chai";
+import ZoomOperatorFactoryChain from "../../../../src/background/operators/impls/ZoomOperatorFactoryChain";
+import MockZoomPresenter from "../../mock/MockZoomPresenter";
+import ZoomInOperator from "../../../../src/background/operators/impls/ZoomInOperator";
+import ZoomOutOperator from "../../../../src/background/operators/impls/ZoomOutOperator";
+import ResetZoomOperator from "../../../../src/background/operators/impls/ResetZoomOperator";
+import * as operations from "../../../../src/shared/operations";
+
+describe("ZoomOperatorFactoryChain", () => {
+ describe("#create", () => {
+ it("returns a operator for the operation", async () => {
+ const zoomPresenter = new MockZoomPresenter();
+ const sut = new ZoomOperatorFactoryChain(zoomPresenter);
+
+ expect(sut.create({ type: operations.ZOOM_IN })).to.be.instanceOf(
+ ZoomInOperator
+ );
+ expect(sut.create({ type: operations.ZOOM_OUT })).to.be.instanceOf(
+ ZoomOutOperator
+ );
+ expect(sut.create({ type: operations.ZOOM_NEUTRAL })).to.be.instanceOf(
+ ResetZoomOperator
+ );
+ expect(sut.create({ type: operations.CANCEL })).to.be.null;
+ });
+ });
+});
diff --git a/test/background/operators/impls/ZoomOutOperator.test.ts b/test/background/operators/impls/ZoomOutOperator.test.ts
new file mode 100644
index 0000000..e0bbcd9
--- /dev/null
+++ b/test/background/operators/impls/ZoomOutOperator.test.ts
@@ -0,0 +1,17 @@
+import sinon from "sinon";
+import ZoomOutOperator from "../../../../src/background/operators/impls/ZoomOutOperator";
+import MockZoomPresenter from "../../mock/MockZoomPresenter";
+
+describe("ZoomOutOperator", () => {
+ describe("#run", () => {
+ it("zoom-in the current tab", async () => {
+ const zoomPresenter = new MockZoomPresenter();
+ const mock = sinon.mock(zoomPresenter).expects("zoomOut").once();
+
+ const sut = new ZoomOutOperator(zoomPresenter);
+ await sut.run();
+
+ mock.verify();
+ });
+ });
+});
diff --git a/test/background/usecases/NavigateUseCase.test.ts b/test/background/usecases/NavigateUseCase.test.ts
deleted file mode 100644
index 086d6cd..0000000
--- a/test/background/usecases/NavigateUseCase.test.ts
+++ /dev/null
@@ -1,183 +0,0 @@
-import "reflect-metadata";
-import TabPresenter from "../../../src/background/presenters/TabPresenter";
-import NavigateUseCase from "../../../src/background/usecases/NavigateUseCase";
-import NavigateClient from "../../../src/background/clients/NavigateClient";
-import * as sinon from "sinon";
-
-class MockTabPresenter implements TabPresenter {
- create(_url: string, _opts?: unknown): Promise<browser.tabs.Tab> {
- throw new Error("not implemented");
- }
-
- duplicate(_id: number): Promise<browser.tabs.Tab> {
- throw new Error("not implemented");
- }
-
- getAll(): Promise<browser.tabs.Tab[]> {
- throw new Error("not implemented");
- }
-
- getByKeyword(
- _keyword: string,
- _excludePinned: boolean
- ): Promise<browser.tabs.Tab[]> {
- throw new Error("not implemented");
- }
-
- getCurrent(): Promise<browser.tabs.Tab> {
- throw new Error("not implemented");
- }
-
- getLastSelectedId(): Promise<number | undefined> {
- throw new Error("not implemented");
- }
-
- getZoom(_tabId: number): Promise<number> {
- throw new Error("not implemented");
- }
-
- onSelected(
- _listener: (arg: { tabId: number; windowId: number }) => void
- ): void {
- throw new Error("not implemented");
- }
-
- open(_url: string, _tabId?: number): Promise<browser.tabs.Tab> {
- throw new Error("not implemented");
- }
-
- reload(_tabId: number, _cache: boolean): Promise<void> {
- throw new Error("not implemented");
- }
-
- remove(_ids: number[]): Promise<void> {
- throw new Error("not implemented");
- }
-
- reopen(): Promise<void> {
- throw new Error("not implemented");
- }
-
- select(_tabId: number): Promise<void> {
- throw new Error("not implemented");
- }
-
- setPinned(_tabId: number, _pinned: boolean): Promise<void> {
- throw new Error("not implemented");
- }
-
- setZoom(_tabId: number, _factor: number): Promise<void> {
- throw new Error("not implemented");
- }
-}
-
-describe("NavigateUseCase", () => {
- let sut: NavigateUseCase;
- let tabPresenter: TabPresenter;
- let navigateClient: NavigateClient;
-
- beforeEach(() => {
- tabPresenter = new MockTabPresenter();
- navigateClient = new NavigateClient();
- sut = new NavigateUseCase(tabPresenter, navigateClient);
- });
-
- const newTab = (url: string): browser.tabs.Tab => {
- return {
- index: 0,
- title: "dummy title",
- url: url,
- active: true,
- hidden: false,
- highlighted: false,
- incognito: false,
- isArticle: false,
- isInReaderMode: false,
- lastAccessed: 1585446733000,
- pinned: false,
- selected: false,
- windowId: 0,
- };
- };
-
- describe("#openParent()", async () => {
- it("opens parent directory of file", async () => {
- sinon
- .stub(tabPresenter, "getCurrent")
- .returns(
- Promise.resolve(newTab("https://google.com/fruits/yellow/banana"))
- );
-
- const mock = sinon
- .mock(tabPresenter)
- .expects("open")
- .withArgs("https://google.com/fruits/yellow/");
-
- await sut.openParent();
-
- mock.verify();
- });
-
- it("opens parent directory of directory", async () => {
- sinon
- .stub(tabPresenter, "getCurrent")
- .returns(Promise.resolve(newTab("https://google.com/fruits/yellow/")));
-
- const mock = sinon
- .mock(tabPresenter)
- .expects("open")
- .withArgs("https://google.com/fruits/");
-
- await sut.openParent();
-
- mock.verify();
- });
-
- it("removes hash", async () => {
- sinon
- .stub(tabPresenter, "getCurrent")
- .returns(Promise.resolve(newTab("https://google.com/#top")));
-
- const mock = sinon
- .mock(tabPresenter)
- .expects("open")
- .withArgs("https://google.com/");
-
- await sut.openParent();
-
- mock.verify();
- });
-
- it("removes search query", async () => {
- sinon
- .stub(tabPresenter, "getCurrent")
- .returns(Promise.resolve(newTab("https://google.com/search?q=apple")));
-
- const mock = sinon
- .mock(tabPresenter)
- .expects("open")
- .withArgs("https://google.com/search");
-
- await sut.openParent();
-
- mock.verify();
- });
- });
-
- describe("#openRoot()", () => {
- it("opens root direectory", async () => {
- sinon
- .stub(tabPresenter, "getCurrent")
- .returns(Promise.resolve(newTab("https://google.com/seach?q=apple")));
-
- const mock = sinon
- .mock(tabPresenter)
- .expects("open")
- .withArgs("https://google.com");
-
- await sut.openRoot();
-
- mock.verify();
- });
- });
-});
diff --git a/test/content/mock/MockAddonEnabledRepository.ts b/test/content/mock/MockAddonEnabledRepository.ts
new file mode 100644
index 0000000..cbe248b
--- /dev/null
+++ b/test/content/mock/MockAddonEnabledRepository.ts
@@ -0,0 +1,18 @@
+import AddonEnabledRepository from "../../../src/content/repositories/AddonEnabledRepository";
+
+export default class MockAddonEnabledRepository
+ implements AddonEnabledRepository {
+ public enabled: boolean;
+
+ constructor(initialValue = false) {
+ this.enabled = initialValue;
+ }
+
+ get(): boolean {
+ return this.enabled;
+ }
+
+ set(on: boolean): void {
+ this.enabled = on;
+ }
+}
diff --git a/test/content/mock/MockAddonIndicatorClient.ts b/test/content/mock/MockAddonIndicatorClient.ts
new file mode 100644
index 0000000..6ea9798
--- /dev/null
+++ b/test/content/mock/MockAddonIndicatorClient.ts
@@ -0,0 +1,13 @@
+import AddonIndicatorClient from "../../../src/content/client/AddonIndicatorClient";
+
+export default class MockAddonIndicatorClient implements AddonIndicatorClient {
+ public enabled: boolean;
+
+ constructor(initialValue = false) {
+ this.enabled = initialValue;
+ }
+
+ async setEnabled(enabled: boolean): Promise<void> {
+ this.enabled = enabled;
+ }
+}
diff --git a/test/content/mock/MockClipboardRepository.ts b/test/content/mock/MockClipboardRepository.ts
new file mode 100644
index 0000000..c6e53bd
--- /dev/null
+++ b/test/content/mock/MockClipboardRepository.ts
@@ -0,0 +1,16 @@
+import ClipboardRepository from "../../../src/content/repositories/ClipboardRepository";
+
+export default class MockClipboardRepository implements ClipboardRepository {
+ private value: string;
+
+ constructor(initValue = "") {
+ this.value = initValue;
+ }
+ read(): string {
+ return this.value;
+ }
+
+ write(text: string): void {
+ this.value = text;
+ }
+}
diff --git a/test/content/mock/MockFindMasterClient.ts b/test/content/mock/MockFindMasterClient.ts
new file mode 100644
index 0000000..a035cc5
--- /dev/null
+++ b/test/content/mock/MockFindMasterClient.ts
@@ -0,0 +1,11 @@
+import FindMasterClient from "../../../src/content/client/FindMasterClient";
+
+export default class MockFindMasterClient implements FindMasterClient {
+ findNext(): void {
+ throw new Error("not implemented");
+ }
+
+ findPrev(): void {
+ throw new Error("not implemented");
+ }
+}
diff --git a/test/content/mock/MockFocusPresenter.ts b/test/content/mock/MockFocusPresenter.ts
new file mode 100644
index 0000000..43454d0
--- /dev/null
+++ b/test/content/mock/MockFocusPresenter.ts
@@ -0,0 +1,7 @@
+import FocusPresenter from "../../../src/content/presenters/FocusPresenter";
+
+export default class MockFocusPresenter implements FocusPresenter {
+ focusFirstElement(): boolean {
+ throw new Error("not implemented");
+ }
+}
diff --git a/test/content/mock/MockFollowMasterClient.ts b/test/content/mock/MockFollowMasterClient.ts
new file mode 100644
index 0000000..fc660d5
--- /dev/null
+++ b/test/content/mock/MockFollowMasterClient.ts
@@ -0,0 +1,16 @@
+import FollowMasterClient from "../../../src/content/client/FollowMasterClient";
+import Key from "../../../src/shared/settings/Key";
+
+export default class MockFollowMasterClient implements FollowMasterClient {
+ responseHintCount(_count: number): void {
+ throw new Error("not implemented");
+ }
+
+ sendKey(_key: Key): void {
+ throw new Error("not implemented");
+ }
+
+ startFollow(_newTab: boolean, _background: boolean): void {
+ throw new Error("not implemented");
+ }
+}
diff --git a/test/content/mock/MockMarkKeyRepository.ts b/test/content/mock/MockMarkKeyRepository.ts
new file mode 100644
index 0000000..12bc75c
--- /dev/null
+++ b/test/content/mock/MockMarkKeyRepository.ts
@@ -0,0 +1,43 @@
+import MarkKeyRepository from "../../../src/content/repositories/MarkKeyRepository";
+
+export default class MockMarkKeyRepository implements MarkKeyRepository {
+ public jumpMode: boolean;
+ public setMode: boolean;
+
+ constructor(
+ initialValue: {
+ jumpMode: boolean;
+ setMode: boolean;
+ } = {
+ jumpMode: false,
+ setMode: false,
+ }
+ ) {
+ this.jumpMode = initialValue.jumpMode;
+ this.setMode = initialValue.setMode;
+ }
+
+ disabeJumpMode(): void {
+ this.jumpMode = false;
+ }
+
+ disabeSetMode(): void {
+ this.setMode = false;
+ }
+
+ enableJumpMode(): void {
+ this.jumpMode = true;
+ }
+
+ enableSetMode(): void {
+ this.setMode = true;
+ }
+
+ isJumpMode(): boolean {
+ return this.jumpMode;
+ }
+
+ isSetMode(): boolean {
+ return this.setMode;
+ }
+}
diff --git a/test/content/mock/MockOperationClient.ts b/test/content/mock/MockOperationClient.ts
new file mode 100644
index 0000000..2f50f77
--- /dev/null
+++ b/test/content/mock/MockOperationClient.ts
@@ -0,0 +1,16 @@
+import OperationClient from "../../../src/content/client/OperationClient";
+import * as operations from "../../../src/shared/operations";
+
+export default class MockOperationClient implements OperationClient {
+ execBackgroundOp(_repeat: number, _op: operations.Operation): Promise<void> {
+ throw new Error("not implemented");
+ }
+
+ internalOpenUrl(
+ _url: string,
+ _newTab?: boolean,
+ _background?: boolean
+ ): Promise<void> {
+ throw new Error("not implemented");
+ }
+}
diff --git a/test/content/mock/MockScrollPresenter.ts b/test/content/mock/MockScrollPresenter.ts
index c802227..8b4cf2a 100644
--- a/test/content/mock/MockScrollPresenter.ts
+++ b/test/content/mock/MockScrollPresenter.ts
@@ -3,10 +3,10 @@ import ScrollPresenter, {
} from "../../../src/content/presenters/ScrollPresenter";
export default class MockScrollPresenter implements ScrollPresenter {
- private pos: Point;
+ private readonly pos: Point;
- constructor() {
- this.pos = { x: 0, y: 0 };
+ constructor(initX = 0, initY = 0) {
+ this.pos = { x: initX, y: initY };
}
getScroll(): Point {
diff --git a/test/content/mock/MockSettingRepository.ts b/test/content/mock/MockSettingRepository.ts
new file mode 100644
index 0000000..5242713
--- /dev/null
+++ b/test/content/mock/MockSettingRepository.ts
@@ -0,0 +1,20 @@
+import SettingRepository from "../../../src/content/repositories/SettingRepository";
+import Settings, {
+ DefaultSetting,
+} from "../../../src/shared/settings/Settings";
+
+export default class MockSettingRepository implements SettingRepository {
+ private value: Settings;
+
+ constructor(initValue: Settings = DefaultSetting) {
+ this.value = initValue;
+ }
+
+ get(): Settings {
+ return this.value;
+ }
+
+ set(setting: Settings): void {
+ this.value = setting;
+ }
+}
diff --git a/test/content/mock/MockURLRepository.ts b/test/content/mock/MockURLRepository.ts
new file mode 100644
index 0000000..731a7fb
--- /dev/null
+++ b/test/content/mock/MockURLRepository.ts
@@ -0,0 +1,9 @@
+import URLRepository from "../../../src/content/operators/impls/URLRepository";
+
+export default class MockURLRepository implements URLRepository {
+ constructor(private url: string = "https://example.com/") {}
+
+ getCurrentURL(): string {
+ return this.url;
+ }
+}
diff --git a/test/content/operators/impls/AddonOperatorFactoryChain.test.ts b/test/content/operators/impls/AddonOperatorFactoryChain.test.ts
new file mode 100644
index 0000000..c064bb9
--- /dev/null
+++ b/test/content/operators/impls/AddonOperatorFactoryChain.test.ts
@@ -0,0 +1,29 @@
+import AddonOperatorFactoryChain from "../../../../src/content/operators/impls/AddonOperatorFactoryChain";
+import EnableAddonOperator from "../../../../src/content/operators/impls/EnableAddonOperator";
+import DisableAddonOperator from "../../../../src/content/operators/impls/DisableAddonOperator";
+import ToggleAddonOperator from "../../../../src/content/operators/impls/ToggleAddonOperator";
+import * as operations from "../../../../src/shared/operations";
+import { expect } from "chai";
+import MockAddonIndicatorClient from "../../mock/MockAddonIndicatorClient";
+import MockAddonEnabledRepository from "../../mock/MockAddonEnabledRepository";
+
+describe("AddonOperatorFactoryChain", () => {
+ describe("#create", () => {
+ it("returns an operator", () => {
+ const sut = new AddonOperatorFactoryChain(
+ new MockAddonIndicatorClient(),
+ new MockAddonEnabledRepository()
+ );
+ expect(sut.create({ type: operations.ADDON_ENABLE }, 0)).to.be.instanceOf(
+ EnableAddonOperator
+ );
+ expect(
+ sut.create({ type: operations.ADDON_DISABLE }, 0)
+ ).to.be.instanceOf(DisableAddonOperator);
+ expect(
+ sut.create({ type: operations.ADDON_TOGGLE_ENABLED }, 0)
+ ).to.be.instanceOf(ToggleAddonOperator);
+ expect(sut.create({ type: operations.SCROLL_TOP }, 0)).to.be.null;
+ });
+ });
+});
diff --git a/test/content/operators/impls/BackgroundOperationOperator.test.ts b/test/content/operators/impls/BackgroundOperationOperator.test.ts
new file mode 100644
index 0000000..b8b1fbd
--- /dev/null
+++ b/test/content/operators/impls/BackgroundOperationOperator.test.ts
@@ -0,0 +1,38 @@
+import * as operations from "../../../../src/shared/operations";
+import BackgroundOperationOperator from "../../../../src/content/operators/impls/BackgroundOperationOperator";
+import OperationClient from "../../../../src/content/client/OperationClient";
+import { expect } from "chai";
+
+class MockOperationClient implements OperationClient {
+ public readonly executedOps: {
+ op: operations.Operation;
+ repeat: number;
+ }[] = [];
+ async execBackgroundOp(
+ repeat: number,
+ op: operations.Operation
+ ): Promise<void> {
+ this.executedOps.push({ repeat, op });
+ }
+
+ internalOpenUrl(): Promise<void> {
+ throw new Error("not implemented");
+ }
+}
+
+describe("BackgroundOperationOperator", () => {
+ describe("#run", () => {
+ it("returns an operator", async () => {
+ const client = new MockOperationClient();
+ const sut = new BackgroundOperationOperator(client, 2, {
+ type: operations.TAB_CLOSE,
+ });
+
+ await sut.run();
+
+ expect(client.executedOps).to.deep.equal([
+ { op: { type: operations.TAB_CLOSE }, repeat: 2 },
+ ]);
+ });
+ });
+});
diff --git a/test/content/operators/impls/ClipboardOperatorFactoryChain.test.ts b/test/content/operators/impls/ClipboardOperatorFactoryChain.test.ts
new file mode 100644
index 0000000..9ddc229
--- /dev/null
+++ b/test/content/operators/impls/ClipboardOperatorFactoryChain.test.ts
@@ -0,0 +1,31 @@
+import * as operations from "../../../../src/shared/operations";
+import { expect } from "chai";
+import ClipboardOperatorFactoryChain from "../../../../src/content/operators/impls/ClipboardOperatorFactoryChain";
+import YankURLOperator from "../../../../src/content/operators/impls/YankURLOperator";
+import PasteOperator from "../../../../src/content/operators/impls/PasteOperator";
+import MockClipboardRepository from "../../mock/MockClipboardRepository";
+import MockOperationClient from "../../mock/MockOperationClient";
+import MockSettingRepository from "../../mock/MockSettingRepository";
+import MockConsoleClient from "../../mock/MockConsoleClient";
+import MockURLRepository from "../../mock/MockURLRepository";
+
+describe("ClipboardOperatorFactoryChain", () => {
+ describe("#create", () => {
+ it("returns an operator", () => {
+ const sut = new ClipboardOperatorFactoryChain(
+ new MockClipboardRepository(),
+ new MockConsoleClient(),
+ new MockOperationClient(),
+ new MockSettingRepository(),
+ new MockURLRepository()
+ );
+ expect(sut.create({ type: operations.URLS_YANK }, 0)).to.be.instanceOf(
+ YankURLOperator
+ );
+ expect(
+ sut.create({ type: operations.URLS_PASTE, newTab: false }, 0)
+ ).to.be.instanceOf(PasteOperator);
+ expect(sut.create({ type: operations.SCROLL_TOP }, 0)).to.be.null;
+ });
+ });
+});
diff --git a/test/content/operators/impls/DisableAddonOperator.test.ts b/test/content/operators/impls/DisableAddonOperator.test.ts
new file mode 100644
index 0000000..358ae44
--- /dev/null
+++ b/test/content/operators/impls/DisableAddonOperator.test.ts
@@ -0,0 +1,19 @@
+import { expect } from "chai";
+import DisableAddonOperator from "../../../../src/content/operators/impls/DisableAddonOperator";
+import MockAddonIndicatorClient from "../../mock/MockAddonIndicatorClient";
+import MockAddonEnabledRepository from "../../mock/MockAddonEnabledRepository";
+
+describe("DisableAddonOperator", () => {
+ describe("#run", () => {
+ it("disables addon", async () => {
+ const client = new MockAddonIndicatorClient(true);
+ const repository = new MockAddonEnabledRepository(true);
+ const sut = new DisableAddonOperator(client, repository);
+
+ await sut.run();
+
+ expect(client.enabled).to.be.false;
+ expect(repository.enabled).to.be.false;
+ });
+ });
+});
diff --git a/test/content/operators/impls/EnableAddonOperator.test.ts b/test/content/operators/impls/EnableAddonOperator.test.ts
new file mode 100644
index 0000000..a6ca31b
--- /dev/null
+++ b/test/content/operators/impls/EnableAddonOperator.test.ts
@@ -0,0 +1,19 @@
+import { expect } from "chai";
+import EnableAddonOperator from "../../../../src/content/operators/impls/EnableAddonOperator";
+import MockAddonIndicatorClient from "../../mock/MockAddonIndicatorClient";
+import MockAddonEnabledRepository from "../../mock/MockAddonEnabledRepository";
+
+describe("EnableAddonOperator", () => {
+ describe("#run", () => {
+ it("enabled addon", async () => {
+ const client = new MockAddonIndicatorClient(false);
+ const repository = new MockAddonEnabledRepository(false);
+ const sut = new EnableAddonOperator(client, repository);
+
+ await sut.run();
+
+ expect(client.enabled).to.be.true;
+ expect(repository.enabled).to.be.true;
+ });
+ });
+});
diff --git a/test/content/operators/impls/EnableJumpMarkOperator.test.ts b/test/content/operators/impls/EnableJumpMarkOperator.test.ts
new file mode 100644
index 0000000..66b4ecd
--- /dev/null
+++ b/test/content/operators/impls/EnableJumpMarkOperator.test.ts
@@ -0,0 +1,19 @@
+import { expect } from "chai";
+import EnableJumpMarkOperator from "../../../../src/content/operators/impls/EnableJumpMarkOperator";
+import MockMarkKeyRepository from "../../mock/MockMarkKeyRepository";
+
+describe("EnableJumpMarkOperator", () => {
+ describe("#run", () => {
+ it("starts mark jump mode", async () => {
+ const repository = new MockMarkKeyRepository({
+ jumpMode: false,
+ setMode: false,
+ });
+ const sut = new EnableJumpMarkOperator(repository);
+
+ await sut.run();
+
+ expect(repository.jumpMode).to.be.true;
+ });
+ });
+});
diff --git a/test/content/operators/impls/EnableSetMarkOperator.test.ts b/test/content/operators/impls/EnableSetMarkOperator.test.ts
new file mode 100644
index 0000000..b28874d
--- /dev/null
+++ b/test/content/operators/impls/EnableSetMarkOperator.test.ts
@@ -0,0 +1,19 @@
+import { expect } from "chai";
+import EnableSetMarkOperator from "../../../../src/content/operators/impls/EnableSetMarkOperator";
+import MockMarkKeyRepository from "../../mock/MockMarkKeyRepository";
+
+describe("EnableSetMarkOperator", () => {
+ describe("#run", () => {
+ it("starts mark set mode", async () => {
+ const repository = new MockMarkKeyRepository({
+ jumpMode: false,
+ setMode: false,
+ });
+ const sut = new EnableSetMarkOperator(repository);
+
+ await sut.run();
+
+ expect(repository.setMode).to.be.true;
+ });
+ });
+});
diff --git a/test/content/operators/impls/FindNextOperator.test.ts b/test/content/operators/impls/FindNextOperator.test.ts
new file mode 100644
index 0000000..d93d45e
--- /dev/null
+++ b/test/content/operators/impls/FindNextOperator.test.ts
@@ -0,0 +1,17 @@
+import sinon from "sinon";
+import FindNextOperator from "../../../../src/content/operators/impls/FindNextOperator";
+import MockFindMasterClient from "../../mock/MockFindMasterClient";
+
+describe("FindNextOperator", () => {
+ describe("#run", () => {
+ it("find next keyword", async () => {
+ const client = new MockFindMasterClient();
+ const mock = sinon.mock(client).expects("findNext").exactly(3);
+ const sut = new FindNextOperator(client, 3);
+
+ await sut.run();
+
+ mock.verify();
+ });
+ });
+});
diff --git a/test/content/operators/impls/FindOperatorFactoryChain.test.ts b/test/content/operators/impls/FindOperatorFactoryChain.test.ts
new file mode 100644
index 0000000..6c599ae
--- /dev/null
+++ b/test/content/operators/impls/FindOperatorFactoryChain.test.ts
@@ -0,0 +1,21 @@
+import * as operations from "../../../../src/shared/operations";
+import { expect } from "chai";
+import FindOperatorFactoryChain from "../../../../src/content/operators/impls/FindOperatorFactoryChain";
+import MockFindMasterClient from "../../mock/MockFindMasterClient";
+import FindNextOperator from "../../../../src/content/operators/impls/FindNextOperator";
+import FindPrevOperator from "../../../../src/content/operators/impls/FindPrevOperator";
+
+describe("FindOperatorFactoryChain", () => {
+ describe("#create", () => {
+ it("returns an operator", () => {
+ const sut = new FindOperatorFactoryChain(new MockFindMasterClient());
+ expect(sut.create({ type: operations.FIND_NEXT }, 0)).to.be.instanceOf(
+ FindNextOperator
+ );
+ expect(sut.create({ type: operations.FIND_PREV }, 0)).to.be.instanceOf(
+ FindPrevOperator
+ );
+ expect(sut.create({ type: operations.SCROLL_TOP }, 0)).to.be.null;
+ });
+ });
+});
diff --git a/test/content/operators/impls/FindPrevOperator.test.ts b/test/content/operators/impls/FindPrevOperator.test.ts
new file mode 100644
index 0000000..1ebde8d
--- /dev/null
+++ b/test/content/operators/impls/FindPrevOperator.test.ts
@@ -0,0 +1,17 @@
+import sinon from "sinon";
+import FindPrevOperator from "../../../../src/content/operators/impls/FindPrevOperator";
+import MockFindMasterClient from "../../mock/MockFindMasterClient";
+
+describe("FindPrevOperator", () => {
+ describe("#run", () => {
+ it("find previous keyword", async () => {
+ const client = new MockFindMasterClient();
+ const mock = sinon.mock(client).expects("findPrev").exactly(3);
+ const sut = new FindPrevOperator(client, 3);
+
+ await sut.run();
+
+ mock.verify();
+ });
+ });
+});
diff --git a/test/content/operators/impls/FocusOperator.test.ts b/test/content/operators/impls/FocusOperator.test.ts
new file mode 100644
index 0000000..a0eb53b
--- /dev/null
+++ b/test/content/operators/impls/FocusOperator.test.ts
@@ -0,0 +1,17 @@
+import sinon from "sinon";
+import FocusOperator from "../../../../src/content/operators/impls/FocusOperator";
+import MockFocusPresenter from "../../mock/MockFocusPresenter";
+
+describe("FocusOperator", () => {
+ describe("#run", () => {
+ it("focus a first input", async () => {
+ const presenter = new MockFocusPresenter();
+ const mock = sinon.mock(presenter).expects("focusFirstElement");
+ const sut = new FocusOperator(presenter);
+
+ await sut.run();
+
+ mock.verify();
+ });
+ });
+});
diff --git a/test/content/operators/impls/FocusOperatorFactoryChain.test.ts b/test/content/operators/impls/FocusOperatorFactoryChain.test.ts
new file mode 100644
index 0000000..91f734b
--- /dev/null
+++ b/test/content/operators/impls/FocusOperatorFactoryChain.test.ts
@@ -0,0 +1,17 @@
+import * as operations from "../../../../src/shared/operations";
+import { expect } from "chai";
+import FocusOperatorFactoryChain from "../../../../src/content/operators/impls/FocusOperatorFactoryChain";
+import FocusOperator from "../../../../src/content/operators/impls/FocusOperator";
+import MockFocusPresenter from "../../mock/MockFocusPresenter";
+
+describe("FocusOperatorFactoryChain", () => {
+ describe("#create", () => {
+ it("returns an operator", () => {
+ const sut = new FocusOperatorFactoryChain(new MockFocusPresenter());
+ expect(sut.create({ type: operations.FOCUS_INPUT }, 0)).to.be.instanceOf(
+ FocusOperator
+ );
+ expect(sut.create({ type: operations.SCROLL_TOP }, 0)).to.be.null;
+ });
+ });
+});
diff --git a/test/content/operators/impls/FollowOperatorFactoryChain.test.ts b/test/content/operators/impls/FollowOperatorFactoryChain.test.ts
new file mode 100644
index 0000000..526a93c
--- /dev/null
+++ b/test/content/operators/impls/FollowOperatorFactoryChain.test.ts
@@ -0,0 +1,20 @@
+import * as operations from "../../../../src/shared/operations";
+import { expect } from "chai";
+import FocusOperatorFactoryChain from "../../../../src/content/operators/impls/FocusOperatorFactoryChain";
+import FocusOperator from "../../../../src/content/operators/impls/FocusOperator";
+import MockFocusPresenter from "../../mock/MockFocusPresenter";
+
+describe("FocusOperatorFactoryChain", () => {
+ describe("#create", () => {
+ it("returns an operator", () => {
+ const sut = new FocusOperatorFactoryChain(new MockFocusPresenter());
+ expect(
+ sut.create(
+ { type: operations.FOCUS_INPUT, newTab: false, background: false },
+ 0
+ )
+ ).to.be.instanceOf(FocusOperator);
+ expect(sut.create({ type: operations.SCROLL_TOP }, 0)).to.be.null;
+ });
+ });
+});
diff --git a/test/content/operators/impls/HorizontalScrollOperator.test.ts b/test/content/operators/impls/HorizontalScrollOperator.test.ts
new file mode 100644
index 0000000..f77a34e
--- /dev/null
+++ b/test/content/operators/impls/HorizontalScrollOperator.test.ts
@@ -0,0 +1,28 @@
+import { expect } from "chai";
+import HorizontalScrollOperator from "../../../../src/content/operators/impls/HorizontalScrollOperator";
+import MockScrollPresenter from "../../mock/MockScrollPresenter";
+import MockSettingRepository from "../../mock/MockSettingRepository";
+
+describe("HorizontalScrollOperator", () => {
+ describe("#run", () => {
+ it("scroll horizontally", async () => {
+ const presenter = new MockScrollPresenter();
+ const settingRepository = new MockSettingRepository();
+ const sut = new HorizontalScrollOperator(presenter, settingRepository, 1);
+
+ await sut.run();
+
+ expect(presenter.getScroll()).to.deep.equal({ x: 1, y: 0 });
+ });
+
+ it("scroll horizontally with repeats", async () => {
+ const presenter = new MockScrollPresenter();
+ const settingRepository = new MockSettingRepository();
+ const sut = new HorizontalScrollOperator(presenter, settingRepository, 5);
+
+ await sut.run();
+
+ expect(presenter.getScroll()).to.deep.equal({ x: 5, y: 0 });
+ });
+ });
+});
diff --git a/test/content/operators/impls/MarkOperatorFactoryChain.test.ts b/test/content/operators/impls/MarkOperatorFactoryChain.test.ts
new file mode 100644
index 0000000..1f094dd
--- /dev/null
+++ b/test/content/operators/impls/MarkOperatorFactoryChain.test.ts
@@ -0,0 +1,21 @@
+import * as operations from "../../../../src/shared/operations";
+import { expect } from "chai";
+import MarkOperatorFactoryChain from "../../../../src/content/operators/impls/MarkOperatorFactoryChain";
+import MockMarkKeyRepository from "../../mock/MockMarkKeyRepository";
+import EnableSetMarkOperator from "../../../../src/content/operators/impls/EnableSetMarkOperator";
+import EnableJumpMarkOperator from "../../../../src/content/operators/impls/EnableJumpMarkOperator";
+
+describe("MarkOperatorFactoryChain", () => {
+ describe("#create", () => {
+ it("returns an operator", () => {
+ const sut = new MarkOperatorFactoryChain(new MockMarkKeyRepository());
+ expect(
+ sut.create({ type: operations.MARK_SET_PREFIX }, 0)
+ ).to.be.instanceOf(EnableSetMarkOperator);
+ expect(
+ sut.create({ type: operations.MARK_JUMP_PREFIX }, 0)
+ ).to.be.instanceOf(EnableJumpMarkOperator);
+ expect(sut.create({ type: operations.SCROLL_TOP }, 0)).to.be.null;
+ });
+ });
+});
diff --git a/test/content/operators/impls/PageScrollOperator.test.ts b/test/content/operators/impls/PageScrollOperator.test.ts
new file mode 100644
index 0000000..80c9185
--- /dev/null
+++ b/test/content/operators/impls/PageScrollOperator.test.ts
@@ -0,0 +1,28 @@
+import { expect } from "chai";
+import PageScrollOperator from "../../../../src/content/operators/impls/PageScrollOperator";
+import MockScrollPresenter from "../../mock/MockScrollPresenter";
+import MockSettingRepository from "../../mock/MockSettingRepository";
+
+describe("PageScrollOperator", () => {
+ describe("#run", () => {
+ it("scroll by a page", async () => {
+ const presenter = new MockScrollPresenter();
+ const settingRepository = new MockSettingRepository();
+ const sut = new PageScrollOperator(presenter, settingRepository, 1);
+
+ await sut.run();
+
+ expect(presenter.getScroll()).to.deep.equal({ x: 1, y: 0 });
+ });
+
+ it("scroll by a page with repeats", async () => {
+ const presenter = new MockScrollPresenter();
+ const settingRepository = new MockSettingRepository();
+ const sut = new PageScrollOperator(presenter, settingRepository, 5);
+
+ await sut.run();
+
+ expect(presenter.getScroll()).to.deep.equal({ x: 5, y: 0 });
+ });
+ });
+});
diff --git a/test/content/operators/impls/PasteOperator.test.ts b/test/content/operators/impls/PasteOperator.test.ts
new file mode 100644
index 0000000..8a3a374
--- /dev/null
+++ b/test/content/operators/impls/PasteOperator.test.ts
@@ -0,0 +1,51 @@
+import sinon from "sinon";
+import PasteOperator from "../../../../src/content/operators/impls/PasteOperator";
+import MockClipboardRepository from "../../mock/MockClipboardRepository";
+import MockSettingRepository from "../../mock/MockSettingRepository";
+import MockOperationClient from "../../mock/MockOperationClient";
+
+describe("PasteOperator", () => {
+ describe("#run", () => {
+ it("open a search url", async () => {
+ const clipboardRepository = new MockClipboardRepository("apple");
+ const settingRepository = new MockSettingRepository();
+ const operationClient = new MockOperationClient();
+ const mockOperationClient = sinon
+ .mock(operationClient)
+ .expects("internalOpenUrl")
+ .withArgs("https://google.com/search?q=apple");
+ const sut = new PasteOperator(
+ clipboardRepository,
+ settingRepository,
+ operationClient,
+ false
+ );
+
+ await sut.run();
+
+ mockOperationClient.verify();
+ });
+
+ it("open a url", async () => {
+ const clipboardRepository = new MockClipboardRepository(
+ "https://example.com/"
+ );
+ const settingRepository = new MockSettingRepository();
+ const operationClient = new MockOperationClient();
+ const mockOperationClient = sinon
+ .mock(operationClient)
+ .expects("internalOpenUrl")
+ .withArgs("https://example.com/");
+ const sut = new PasteOperator(
+ clipboardRepository,
+ settingRepository,
+ operationClient,
+ false
+ );
+
+ await sut.run();
+
+ mockOperationClient.verify();
+ });
+ });
+});
diff --git a/test/content/operators/impls/ScrollOperatorFactoryChain.test.ts b/test/content/operators/impls/ScrollOperatorFactoryChain.test.ts
new file mode 100644
index 0000000..08034cb
--- /dev/null
+++ b/test/content/operators/impls/ScrollOperatorFactoryChain.test.ts
@@ -0,0 +1,46 @@
+import { expect } from "chai";
+import ScrollOperatorFactoryChain from "../../../../src/content/operators/impls/ScrollOperatorFactoryChain";
+import MockScrollPresenter from "../../mock/MockScrollPresenter";
+import MockSettingRepository from "../../mock/MockSettingRepository";
+import HorizontalScrollOperator from "../../../../src/content/operators/impls/HorizontalScrollOperator";
+import VerticalScrollOperator from "../../../../src/content/operators/impls/VerticalScrollOperator";
+import PageScrollOperator from "../../../../src/content/operators/impls/PageScrollOperator";
+import ScrollToTopOperator from "../../../../src/content/operators/impls/ScrollToTopOperator";
+import ScrollToBottomOperator from "../../../../src/content/operators/impls/ScrollToBottomOperator";
+import ScrollToHomeOperator from "../../../../src/content/operators/impls/ScrollToHomeOperator";
+import ScrollToEndOperator from "../../../../src/content/operators/impls/ScrollToEndOperator";
+import * as operations from "../../../../src/shared/operations";
+
+describe("ScrollOperatorFactoryChain", () => {
+ describe("#create", () => {
+ it("returns an operator", () => {
+ const sut = new ScrollOperatorFactoryChain(
+ new MockScrollPresenter(),
+ new MockSettingRepository()
+ );
+ expect(
+ sut.create({ type: operations.SCROLL_HORIZONALLY, count: 10 }, 0)
+ ).to.be.instanceOf(HorizontalScrollOperator);
+ expect(
+ sut.create({ type: operations.SCROLL_VERTICALLY, count: 10 }, 0)
+ ).to.be.instanceOf(VerticalScrollOperator);
+ expect(
+ sut.create({ type: operations.SCROLL_PAGES, count: 10 }, 0)
+ ).to.be.instanceOf(PageScrollOperator);
+ expect(sut.create({ type: operations.SCROLL_TOP }, 0)).to.be.instanceOf(
+ ScrollToTopOperator
+ );
+ expect(
+ sut.create({ type: operations.SCROLL_BOTTOM }, 0)
+ ).to.be.instanceOf(ScrollToBottomOperator);
+ expect(sut.create({ type: operations.SCROLL_HOME }, 0)).to.be.instanceOf(
+ ScrollToHomeOperator
+ );
+ expect(sut.create({ type: operations.SCROLL_END }, 0)).to.be.instanceOf(
+ ScrollToEndOperator
+ );
+ expect(sut.create({ type: operations.PAGE_HOME, newTab: false }, 0)).to.be
+ .null;
+ });
+ });
+});
diff --git a/test/content/operators/impls/ScrollToBottomOperator.test.ts b/test/content/operators/impls/ScrollToBottomOperator.test.ts
new file mode 100644
index 0000000..500c8f2
--- /dev/null
+++ b/test/content/operators/impls/ScrollToBottomOperator.test.ts
@@ -0,0 +1,18 @@
+import { expect } from "chai";
+import ScrollToBottomOperator from "../../../../src/content/operators/impls/ScrollToBottomOperator";
+import MockScrollPresenter from "../../mock/MockScrollPresenter";
+import MockSettingRepository from "../../mock/MockSettingRepository";
+
+describe("ScrollToBottomOperator", () => {
+ describe("#run", () => {
+ it("scroll to bottom", async () => {
+ const presenter = new MockScrollPresenter();
+ const settingRepository = new MockSettingRepository();
+ const sut = new ScrollToBottomOperator(presenter, settingRepository);
+
+ await sut.run();
+
+ expect(presenter.getScroll()).to.deep.equal({ x: 0, y: Infinity });
+ });
+ });
+});
diff --git a/test/content/operators/impls/ScrollToEndOperator.test.ts b/test/content/operators/impls/ScrollToEndOperator.test.ts
new file mode 100644
index 0000000..0c98c8d
--- /dev/null
+++ b/test/content/operators/impls/ScrollToEndOperator.test.ts
@@ -0,0 +1,18 @@
+import { expect } from "chai";
+import ScrollToEndOperator from "../../../../src/content/operators/impls/ScrollToEndOperator";
+import MockScrollPresenter from "../../mock/MockScrollPresenter";
+import MockSettingRepository from "../../mock/MockSettingRepository";
+
+describe("ScrollToEndOperator", () => {
+ describe("#run", () => {
+ it("scroll to rightmost", async () => {
+ const presenter = new MockScrollPresenter();
+ const settingRepository = new MockSettingRepository();
+ const sut = new ScrollToEndOperator(presenter, settingRepository);
+
+ await sut.run();
+
+ expect(presenter.getScroll()).to.deep.equal({ x: Infinity, y: 0 });
+ });
+ });
+});
diff --git a/test/content/operators/impls/ScrollToHomeOperator.test.ts b/test/content/operators/impls/ScrollToHomeOperator.test.ts
new file mode 100644
index 0000000..f8614d2
--- /dev/null
+++ b/test/content/operators/impls/ScrollToHomeOperator.test.ts
@@ -0,0 +1,18 @@
+import { expect } from "chai";
+import ScrollToHomeOperator from "../../../../src/content/operators/impls/ScrollToHomeOperator";
+import MockScrollPresenter from "../../mock/MockScrollPresenter";
+import MockSettingRepository from "../../mock/MockSettingRepository";
+
+describe("ScrollToHomeOperator", () => {
+ describe("#run", () => {
+ it("scroll to leftmost", async () => {
+ const presenter = new MockScrollPresenter(10, 10);
+ const settingRepository = new MockSettingRepository();
+ const sut = new ScrollToHomeOperator(presenter, settingRepository);
+
+ await sut.run();
+
+ expect(presenter.getScroll()).to.deep.equal({ x: 0, y: 10 });
+ });
+ });
+});
diff --git a/test/content/operators/impls/ScrollToTopOperator.test.ts b/test/content/operators/impls/ScrollToTopOperator.test.ts
new file mode 100644
index 0000000..25a84ba
--- /dev/null
+++ b/test/content/operators/impls/ScrollToTopOperator.test.ts
@@ -0,0 +1,18 @@
+import { expect } from "chai";
+import ScrollToTopOperator from "../../../../src/content/operators/impls/ScrollToTopOperator";
+import MockScrollPresenter from "../../mock/MockScrollPresenter";
+import MockSettingRepository from "../../mock/MockSettingRepository";
+
+describe("ScrollToTopOperator", () => {
+ describe("#run", () => {
+ it("scroll to top", async () => {
+ const presenter = new MockScrollPresenter(10, 10);
+ const settingRepository = new MockSettingRepository();
+ const sut = new ScrollToTopOperator(presenter, settingRepository);
+
+ await sut.run();
+
+ expect(presenter.getScroll()).to.deep.equal({ x: 10, y: 0 });
+ });
+ });
+});
diff --git a/test/content/operators/impls/StartFollowOperator.test.ts b/test/content/operators/impls/StartFollowOperator.test.ts
new file mode 100644
index 0000000..8f9bd2d
--- /dev/null
+++ b/test/content/operators/impls/StartFollowOperator.test.ts
@@ -0,0 +1,20 @@
+import sinon from "sinon";
+import StartFollowOperator from "../../../../src/content/operators/impls/StartFollowOperator";
+import MockFollowMasterClient from "../../mock/MockFollowMasterClient";
+
+describe("StartFollowOperator", () => {
+ describe("#run", () => {
+ it("starts following links", async () => {
+ const client = new MockFollowMasterClient();
+ const mock = sinon
+ .mock(client)
+ .expects("startFollow")
+ .withArgs(true, false);
+ const sut = new StartFollowOperator(client, true, false);
+
+ await sut.run();
+
+ mock.verify();
+ });
+ });
+});
diff --git a/test/content/operators/impls/ToggleAddonOperator.test.ts b/test/content/operators/impls/ToggleAddonOperator.test.ts
new file mode 100644
index 0000000..6026eb1
--- /dev/null
+++ b/test/content/operators/impls/ToggleAddonOperator.test.ts
@@ -0,0 +1,24 @@
+import { expect } from "chai";
+import ToggleAddonOperator from "../../../../src/content/operators/impls/ToggleAddonOperator";
+import MockAddonIndicatorClient from "../../mock/MockAddonIndicatorClient";
+import MockAddonEnabledRepository from "../../mock/MockAddonEnabledRepository";
+
+describe("ToggleAddonOperator", () => {
+ describe("#run", () => {
+ it("toggles addon-enabled state", async () => {
+ const client = new MockAddonIndicatorClient(true);
+ const repository = new MockAddonEnabledRepository(true);
+ const sut = new ToggleAddonOperator(client, repository);
+
+ await sut.run();
+
+ expect(client.enabled).to.be.false;
+ expect(repository.enabled).to.be.false;
+
+ await sut.run();
+
+ expect(client.enabled).to.be.true;
+ expect(repository.enabled).to.be.true;
+ });
+ });
+});
diff --git a/test/content/operators/impls/VerticalScrollOperator.test.ts b/test/content/operators/impls/VerticalScrollOperator.test.ts
new file mode 100644
index 0000000..05b15d2
--- /dev/null
+++ b/test/content/operators/impls/VerticalScrollOperator.test.ts
@@ -0,0 +1,28 @@
+import { expect } from "chai";
+import VerticalScrollOperator from "../../../../src/content/operators/impls/VerticalScrollOperator";
+import MockScrollPresenter from "../../mock/MockScrollPresenter";
+import MockSettingRepository from "../../mock/MockSettingRepository";
+
+describe("VerticalScrollOperator", () => {
+ describe("#run", () => {
+ it("scroll vertically", async () => {
+ const presenter = new MockScrollPresenter();
+ const settingRepository = new MockSettingRepository();
+ const sut = new VerticalScrollOperator(presenter, settingRepository, 1);
+
+ await sut.run();
+
+ expect(presenter.getScroll()).to.deep.equal({ x: 0, y: 1 });
+ });
+
+ it("scroll vertically with repeats", async () => {
+ const presenter = new MockScrollPresenter();
+ const settingRepository = new MockSettingRepository();
+ const sut = new VerticalScrollOperator(presenter, settingRepository, 5);
+
+ await sut.run();
+
+ expect(presenter.getScroll()).to.deep.equal({ x: 0, y: 5 });
+ });
+ });
+});
diff --git a/test/content/operators/impls/YankURLOperator.test.ts b/test/content/operators/impls/YankURLOperator.test.ts
new file mode 100644
index 0000000..46e3d06
--- /dev/null
+++ b/test/content/operators/impls/YankURLOperator.test.ts
@@ -0,0 +1,26 @@
+import { expect } from "chai";
+import MockClipboardRepository from "../../mock/MockClipboardRepository";
+import YankURLOperator from "../../../../src/content/operators/impls/YankURLOperator";
+import MockURLRepository from "../../mock/MockURLRepository";
+import MockConsoleClient from "../../mock/MockConsoleClient";
+
+describe("YankOperation", () => {
+ describe("#run", () => {
+ it("copy current URL", async () => {
+ const clipboardRepository = new MockClipboardRepository();
+ const consoleClient = new MockConsoleClient();
+ const urlRepository = new MockURLRepository("https://example.com/");
+ const sut = new YankURLOperator(
+ clipboardRepository,
+ consoleClient,
+ urlRepository
+ );
+
+ await sut.run();
+
+ expect(clipboardRepository.read()).to.equal("https://example.com/");
+ expect(consoleClient.text).to.equal("Yanked https://example.com/");
+ expect(consoleClient.isError).to.be.false;
+ });
+ });
+});
diff --git a/test/content/usecases/ClipboardUseCase.test.ts b/test/content/usecases/ClipboardUseCase.test.ts
deleted file mode 100644
index 5de3e69..0000000
--- a/test/content/usecases/ClipboardUseCase.test.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-import ClipboardRepository from "../../../src/content/repositories/ClipboardRepository";
-import { SettingRepositoryImpl } from "../../../src/content/repositories/SettingRepository";
-import ClipboardUseCase from "../../../src/content/usecases/ClipboardUseCase";
-import OperationClient from "../../../src/content/client/OperationClient";
-import ConsoleClient from "../../../src/content/client/ConsoleClient";
-
-import * as sinon from "sinon";
-import { expect } from "chai";
-import { Operation } from "../../../src/shared/operations";
-
-describe("ClipboardUseCase", () => {
- let clipboardRepository: ClipboardRepository;
-
- let operationClient: OperationClient;
-
- let consoleClient: ConsoleClient;
-
- let sut: ClipboardUseCase;
-
- beforeEach(() => {
- clipboardRepository = new (class implements ClipboardRepository {
- read(): string {
- return "";
- }
- write(_text: string) {}
- })();
- operationClient = new (class implements OperationClient {
- execBackgroundOp(_repeat: number, _op: Operation): Promise<void> {
- return Promise.resolve();
- }
- internalOpenUrl(
- _url: string,
- _newTab?: boolean,
- _background?: boolean
- ): Promise<void> {
- return Promise.resolve();
- }
- })();
- consoleClient = new (class implements ConsoleClient {
- error(_text: string): Promise<void> {
- return Promise.resolve();
- }
- info(_text: string): Promise<void> {
- return Promise.resolve();
- }
- })();
-
- sut = new ClipboardUseCase(
- clipboardRepository,
- new SettingRepositoryImpl(),
- consoleClient,
- operationClient
- );
- });
-
- describe("#yankCurrentURL", () => {
- it("yanks current url", async () => {
- const href = window.location.href;
- const mockRepository = sinon.mock(clipboardRepository);
- mockRepository.expects("write").withArgs(href);
- const mockConsoleClient = sinon.mock(consoleClient);
- mockConsoleClient.expects("info").withArgs("Yanked " + href);
-
- const yanked = await sut.yankCurrentURL();
-
- expect(yanked).to.equal(href);
- mockRepository.verify();
- mockConsoleClient.verify();
- });
- });
-
- describe("#openOrSearch", () => {
- it("opens url from the clipboard", async () => {
- const url = "https://github.com/ueokande/vim-vixen";
- sinon.stub(clipboardRepository, "read").returns(url);
- const mockOperationClient = sinon.mock(operationClient);
- mockOperationClient.expects("internalOpenUrl").withArgs(url, true);
-
- await sut.openOrSearch(true);
-
- mockOperationClient.verify();
- });
-
- it("opens search results from the clipboard", async () => {
- const url = "https://google.com/search?q=banana";
- sinon.stub(clipboardRepository, "read").returns("banana");
- const mockOperationClient = sinon.mock(operationClient);
- mockOperationClient.expects("internalOpenUrl").withArgs(url, true);
-
- await sut.openOrSearch(true);
-
- mockOperationClient.verify();
- });
- });
-});
diff --git a/test/content/usecases/HintKeyProducer.test.ts b/test/content/usecases/HintKeyProducer.test.ts
index f7e02ea..9d320b4 100644
--- a/test/content/usecases/HintKeyProducer.test.ts
+++ b/test/content/usecases/HintKeyProducer.test.ts
@@ -1,13 +1,7 @@
-import HintKeyProducer from "../../../src/content/usecases/HintKeyProducer";
+import { HintKeyRepositoryImpl } from "../../../src/content/repositories/HintKeyRepository";
import { expect } from "chai";
-describe("HintKeyProducer class", () => {
- describe("#constructor", () => {
- it("throws an exception on empty charset", () => {
- expect(() => new HintKeyProducer("")).to.throw(TypeError);
- });
- });
-
+describe("HintKeyProducerImpl class", () => {
describe("#produce", () => {
it("produce incremented keys", () => {
const charset = "abc";
@@ -30,10 +24,29 @@ describe("HintKeyProducer class", () => {
"aba",
];
- const producer = new HintKeyProducer(charset);
+ const sut = new HintKeyRepositoryImpl();
+ sut.reset(charset);
for (let i = 0; i < sequences.length; ++i) {
- expect(producer.produce()).to.equal(sequences[i]);
+ expect(sut.produce()).to.equal(sequences[i]);
}
});
});
+
+ describe("#reset", () => {
+ it("resets charset", () => {
+ const sut = new HintKeyRepositoryImpl();
+
+ sut.reset("ab");
+ expect(sut.produce()).to.equal("a");
+ expect(sut.produce()).to.equal("b");
+
+ sut.reset("xy");
+ expect(sut.produce()).to.equal("x");
+ expect(sut.produce()).to.equal("y");
+ });
+ it("throws an exception on empty charset", () => {
+ const sut = new HintKeyRepositoryImpl();
+ expect(() => sut.reset("")).to.throw(TypeError);
+ });
+ });
});
diff --git a/yarn.lock b/yarn.lock
index dfa3bd4..d9566bc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -211,9 +211,9 @@
integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==
"@types/assert@^1.4.6":
- version "1.5.1"
- resolved "https://registry.yarnpkg.com/@types/assert/-/assert-1.5.1.tgz#9c4b57935d446ffcfb31d583799c47272161e270"
- integrity sha512-W34OXyUAXEg0IvkMBr6WHTsvxh4veqNVJJGni/6pp13BabT/nuiEtUVkf+EYo8FIT4CQSVYfZ05XtUo5nRSWfQ==
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/@types/assert/-/assert-1.5.2.tgz#dbc440f6bd7a83b03c37c65e81076d07cf8becdc"
+ integrity sha512-DLsoZH9z5DLDi6qMbXKqeqlQLK1h3rfR9dK+KX8UJSGHJylvIZPOCQEKr/d/FClPoZE/eHOa3+e270eUJCUTog==
"@types/body-parser@*":
version "1.19.0"
@@ -683,6 +683,18 @@
"@webassemblyjs/wast-parser" "1.9.0"
"@xtuc/long" "4.2.2"
+"@webpack-cli/info@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.1.0.tgz#c596d5bc48418b39df00c5ed7341bf0f102dbff1"
+ integrity sha512-uNWSdaYHc+f3LdIZNwhdhkjjLDDl3jP2+XBqAq9H8DjrJUvlOKdP8TNruy1yEaDfgpAIgbSAN7pye4FEHg9tYQ==
+ dependencies:
+ envinfo "^7.7.3"
+
+"@webpack-cli/serve@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.1.0.tgz#13ad38f89b6e53d1133bac0006a128217a6ebf92"
+ integrity sha512-7RfnMXCpJ/NThrhq4gYQYILB18xWyoQcBey81oIyVbmgbc6m5ZHHyFK+DyH7pLHJf0p14MxL4mTsoPAgBSTpIg==
+
"@xtuc/ieee754@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
@@ -768,17 +780,7 @@ ajv@^5.0.0:
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.3.0"
-ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.11.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.7.0:
- version "6.12.5"
- resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da"
- integrity sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==
- dependencies:
- fast-deep-equal "^3.1.1"
- fast-json-stable-stringify "^2.0.0"
- json-schema-traverse "^0.4.1"
- uri-js "^4.2.2"
-
-ajv@^6.12.5:
+ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.11.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.7.0:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
@@ -893,6 +895,11 @@ arr-union@^3.1.0:
resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=
+array-back@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.1.tgz#9b80312935a52062e1a233a9c7abeb5481b30e90"
+ integrity sha512-Z/JnaVEXv+A9xabHzN43FiiiWEE7gPCRXMrVmRm00tWbjZRul1iHm7ECzlyNq1p4a4ATXz+G9FJ3GqGOkOV3fg==
+
array-flatten@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
@@ -1552,6 +1559,11 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+colorette@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b"
+ integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==
+
colors@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
@@ -1564,6 +1576,16 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
+command-line-usage@^6.1.0:
+ version "6.1.1"
+ resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-6.1.1.tgz#c908e28686108917758a49f45efb4f02f76bc03f"
+ integrity sha512-F59pEuAR9o1SF/bD0dQBDluhpT4jJQNWUHEuVBqpDmCUo6gPjCi+m9fCWnWZVR/oG6cMTUms4h+3NPl74wGXvA==
+ dependencies:
+ array-back "^4.0.1"
+ chalk "^2.4.2"
+ table-layout "^1.0.1"
+ typical "^5.2.0"
+
commander@^2.19.0, commander@^2.20.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
@@ -1574,6 +1596,11 @@ commander@^4.1.1:
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
+commander@^6.2.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.0.tgz#b990bfb8ac030aedc6d11bc04d1488ffef56db75"
+ integrity sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==
+
common-tags@^1.4.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937"
@@ -1738,7 +1765,7 @@ cross-spawn@^6.0.5:
shebang-command "^1.2.0"
which "^1.2.9"
-cross-spawn@^7.0.2:
+cross-spawn@^7.0.0, cross-spawn@^7.0.2:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
@@ -1886,6 +1913,11 @@ deep-eql@^3.0.1:
dependencies:
type-detect "^4.0.0"
+deep-extend@~0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
+ integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
+
deep-is@^0.1.3, deep-is@~0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
@@ -1943,11 +1975,6 @@ destroy@~1.0.4:
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
-detect-file@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7"
- integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=
-
di@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c"
@@ -2182,7 +2209,7 @@ engine.io@~3.4.0:
engine.io-parser "~2.2.0"
ws "^7.1.2"
-enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.1, enhanced-resolve@^4.3.0:
+enhanced-resolve@^4.0.0, enhanced-resolve@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz#3b806f3bfafc1ec7de69551ef93cca46c1704126"
integrity sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==
@@ -2191,7 +2218,7 @@ enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.1, enhanced-resolve@^4.3.0:
memory-fs "^0.5.0"
tapable "^1.0.0"
-enquirer@^2.3.5:
+enquirer@^2.3.5, enquirer@^2.3.6:
version "2.3.6"
resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
@@ -2213,6 +2240,11 @@ entities@^2.0.0:
resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f"
integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==
+envinfo@^7.7.3:
+ version "7.7.3"
+ resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.7.3.tgz#4b2d8622e3e7366afb8091b23ed95569ea0208cc"
+ integrity sha512-46+j5QxbPWza0PB1i15nZx0xQ4I/EfQxg9J8Had3b408SV63nEtor2e+oiY63amTo9KTuh2a3XLObNwduxYwwA==
+
errno@^0.1.3, errno@~0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618"
@@ -2541,6 +2573,21 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
md5.js "^1.3.4"
safe-buffer "^5.1.1"
+execa@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a"
+ integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==
+ dependencies:
+ cross-spawn "^7.0.0"
+ get-stream "^5.0.0"
+ human-signals "^1.1.1"
+ is-stream "^2.0.0"
+ merge-stream "^2.0.0"
+ npm-run-path "^4.0.0"
+ onetime "^5.1.0"
+ signal-exit "^3.0.2"
+ strip-final-newline "^2.0.0"
+
expand-brackets@^2.1.4:
version "2.1.4"
resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622"
@@ -2554,13 +2601,6 @@ expand-brackets@^2.1.4:
snapdragon "^0.8.1"
to-regex "^3.0.1"
-expand-tilde@^2.0.0, expand-tilde@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502"
- integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=
- dependencies:
- homedir-polyfill "^1.0.1"
-
express@^4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
@@ -2765,7 +2805,7 @@ find-up@^3.0.0:
dependencies:
locate-path "^3.0.0"
-find-up@^4.1.0:
+find-up@^4.0.0, find-up@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
@@ -2773,16 +2813,6 @@ find-up@^4.1.0:
locate-path "^5.0.0"
path-exists "^4.0.0"
-findup-sync@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1"
- integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==
- dependencies:
- detect-file "^1.0.0"
- is-glob "^4.0.0"
- micromatch "^3.0.4"
- resolve-dir "^1.0.1"
-
flat-cache@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0"
@@ -2930,6 +2960,13 @@ get-stdin@^6.0.0:
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==
+get-stream@^5.0.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
+ integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==
+ dependencies:
+ pump "^3.0.0"
+
get-value@^2.0.3, get-value@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
@@ -2969,42 +3006,6 @@ glob@7.1.6, glob@^7.1.0, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
once "^1.3.0"
path-is-absolute "^1.0.0"
-global-modules@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea"
- integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==
- dependencies:
- global-prefix "^1.0.1"
- is-windows "^1.0.1"
- resolve-dir "^1.0.0"
-
-global-modules@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780"
- integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==
- dependencies:
- global-prefix "^3.0.0"
-
-global-prefix@^1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe"
- integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=
- dependencies:
- expand-tilde "^2.0.2"
- homedir-polyfill "^1.0.1"
- ini "^1.3.4"
- is-windows "^1.0.1"
- which "^1.2.14"
-
-global-prefix@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97"
- integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==
- dependencies:
- ini "^1.3.5"
- kind-of "^6.0.2"
- which "^1.3.1"
-
globals@^11.1.0:
version "11.12.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
@@ -3150,13 +3151,6 @@ hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0:
dependencies:
react-is "^16.7.0"
-homedir-polyfill@^1.0.1:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8"
- integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==
- dependencies:
- parse-passwd "^1.0.0"
-
html-minifier-terser@^5.0.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#922e96f1f3bb60832c2634b79884096389b1f054"
@@ -3242,6 +3236,11 @@ https-browserify@^1.0.0:
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
+human-signals@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
+ integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==
+
iconv-lite@0.4.24, iconv-lite@^0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@@ -3284,13 +3283,13 @@ import-fresh@^3.0.0, import-fresh@^3.2.1:
parent-module "^1.0.0"
resolve-from "^4.0.0"
-import-local@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d"
- integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==
+import-local@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6"
+ integrity sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==
dependencies:
- pkg-dir "^3.0.0"
- resolve-cwd "^2.0.0"
+ pkg-dir "^4.2.0"
+ resolve-cwd "^3.0.0"
imurmurhash@^0.1.4:
version "0.1.4"
@@ -3340,10 +3339,10 @@ inherits@2.0.3:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
-ini@^1.3.4, ini@^1.3.5:
- version "1.3.5"
- resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
- integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
+ini@^1.3.4:
+ version "1.3.8"
+ resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
+ integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
inquirer@^7.0.0:
version "7.3.3"
@@ -3373,10 +3372,10 @@ internal-slot@^1.0.2:
has "^1.0.3"
side-channel "^1.0.2"
-interpret@^1.4.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
- integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
+interpret@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9"
+ integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==
ipaddr.js@1.9.1:
version "1.9.1"
@@ -3436,6 +3435,13 @@ is-callable@^1.2.2:
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9"
integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==
+is-core-module@^2.1.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a"
+ integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==
+ dependencies:
+ has "^1.0.3"
+
is-data-descriptor@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
@@ -3570,6 +3576,11 @@ is-set@^2.0.1:
resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.1.tgz#d1604afdab1724986d30091575f54945da7e5f43"
integrity sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==
+is-stream@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3"
+ integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==
+
is-string@^1.0.4, is-string@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6"
@@ -3587,7 +3598,7 @@ is-typedarray@~1.0.0:
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
-is-windows@^1.0.1, is-windows@^1.0.2:
+is-windows@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
@@ -3939,6 +3950,11 @@ lanthan@0.0.2:
request-promise-native "^1.0.7"
selenium-webdriver "^4.0.0-alpha.5"
+leven@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
+ integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==
+
levn@^0.3.0, levn@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
@@ -3967,7 +3983,7 @@ loader-runner@^2.4.0:
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==
-loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0:
+loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
@@ -4201,12 +4217,17 @@ merge-descriptors@1.0.1:
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
+merge-stream@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
+ integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
+
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
-micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4:
+micromatch@^3.1.10, micromatch@^3.1.4:
version "3.1.10"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==
@@ -4510,6 +4531,13 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+npm-run-path@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
+ integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==
+ dependencies:
+ path-key "^3.0.0"
+
nth-check@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c"
@@ -4771,11 +4799,6 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.5:
pbkdf2 "^3.0.3"
safe-buffer "^5.1.1"
-parse-passwd@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
- integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=
-
parseqs@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d"
@@ -4838,7 +4861,7 @@ path-key@^2.0.1:
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
-path-key@^3.1.0:
+path-key@^3.0.0, path-key@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
@@ -4898,6 +4921,13 @@ pkg-dir@^3.0.0:
dependencies:
find-up "^3.0.0"
+pkg-dir@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
+ integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==
+ dependencies:
+ find-up "^4.0.0"
+
posix-character-classes@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
@@ -4994,10 +5024,10 @@ prettier-linter-helpers@^1.0.0:
dependencies:
fast-diff "^1.1.2"
-prettier@2.2.0, prettier@^2.0.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.0.tgz#8a03c7777883b29b37fb2c4348c66a78e980418b"
- integrity sha512-yYerpkvseM4iKD/BXLYUkQV5aKt4tQPqaGW6EsZjzyu0r7sVZZNPJW4Y8MyKmicp6t42XUPcBVA+H6sB3gqndw==
+prettier@2.2.1, prettier@^2.0.0:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5"
+ integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==
pretty-error@^2.1.1:
version "2.1.1"
@@ -5273,6 +5303,18 @@ readdirp@~3.4.0:
dependencies:
picomatch "^2.2.1"
+rechoir@^0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.0.tgz#32650fd52c21ab252aa5d65b19310441c7e03aca"
+ integrity sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==
+ dependencies:
+ resolve "^1.9.0"
+
+reduce-flatten@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27"
+ integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==
+
redux-promise@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/redux-promise/-/redux-promise-0.6.0.tgz#c64723b5213bb5603c11b74302883b682e06b319"
@@ -5433,31 +5475,23 @@ requires-port@^1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
-resolve-cwd@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
- integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=
- dependencies:
- resolve-from "^3.0.0"
-
-resolve-dir@^1.0.0, resolve-dir@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43"
- integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=
- dependencies:
- expand-tilde "^2.0.0"
- global-modules "^1.0.0"
-
-resolve-from@^3.0.0:
+resolve-cwd@^3.0.0:
version "3.0.0"
- resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
- integrity sha1-six699nWiBvItuZTM17rywoYh0g=
+ resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
+ integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==
+ dependencies:
+ resolve-from "^5.0.0"
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
+resolve-from@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
+ integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
+
resolve-url@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
@@ -5470,6 +5504,14 @@ resolve@^1.17.0:
dependencies:
path-parse "^1.0.6"
+resolve@^1.9.0:
+ version "1.19.0"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c"
+ integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==
+ dependencies:
+ is-core-module "^2.1.0"
+ path-parse "^1.0.6"
+
restore-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
@@ -6114,6 +6156,11 @@ strip-ansi@^6.0.0:
dependencies:
ansi-regex "^5.0.0"
+strip-final-newline@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
+ integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
+
strip-json-comments@3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7"
@@ -6179,6 +6226,16 @@ symbol-observable@^1.0.3, symbol-observable@^1.2.0:
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
+table-layout@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-1.0.1.tgz#8411181ee951278ad0638aea2f779a9ce42894f9"
+ integrity sha512-dEquqYNJiGwY7iPfZ3wbXDI944iqanTSchrACLL2nOB+1r+h1Nzu2eH+DuPPvWvm5Ry7iAPeFlgEtP5bIp5U7Q==
+ dependencies:
+ array-back "^4.0.1"
+ deep-extend "~0.6.0"
+ typical "^5.2.0"
+ wordwrapjs "^4.0.0"
+
table@^5.2.3:
version "5.4.6"
resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"
@@ -6434,6 +6491,11 @@ typescript@^3.9.3:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa"
integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==
+typical@^5.0.0, typical@^5.2.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/typical/-/typical-5.2.0.tgz#4daaac4f2b5315460804f0acf6cb69c52bb93066"
+ integrity sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==
+
ua-parser-js@0.7.22:
version "0.7.22"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.22.tgz#960df60a5f911ea8f1c818f3747b99c6e177eae3"
@@ -6563,10 +6625,10 @@ uuid@^3.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
-v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745"
- integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==
+v8-compile-cache@^2.0.3, v8-compile-cache@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132"
+ integrity sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==
vary@~1.1.2:
version "1.1.2"
@@ -6634,22 +6696,24 @@ webextensions-api-fake@^0.9.1:
dependencies:
sinon-chrome "^3.0.0"
-webpack-cli@3.3.12:
- version "3.3.12"
- resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.12.tgz#94e9ada081453cd0aa609c99e500012fd3ad2d4a"
- integrity sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag==
- dependencies:
- chalk "^2.4.2"
- cross-spawn "^6.0.5"
- enhanced-resolve "^4.1.1"
- findup-sync "^3.0.0"
- global-modules "^2.0.0"
- import-local "^2.0.0"
- interpret "^1.4.0"
- loader-utils "^1.4.0"
- supports-color "^6.1.0"
- v8-compile-cache "^2.1.1"
- yargs "^13.3.2"
+webpack-cli@4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.2.0.tgz#10a09030ad2bd4d8b0f78322fba6ea43ec56aaaa"
+ integrity sha512-EIl3k88vaF4fSxWSgtAQR+VwicfLMTZ9amQtqS4o+TDPW9HGaEpbFBbAZ4A3ZOT5SOnMxNOzROsSTPiE8tBJPA==
+ dependencies:
+ "@webpack-cli/info" "^1.1.0"
+ "@webpack-cli/serve" "^1.1.0"
+ colorette "^1.2.1"
+ command-line-usage "^6.1.0"
+ commander "^6.2.0"
+ enquirer "^2.3.6"
+ execa "^4.1.0"
+ import-local "^3.0.2"
+ interpret "^2.2.0"
+ leven "^3.1.0"
+ rechoir "^0.7.0"
+ v8-compile-cache "^2.2.0"
+ webpack-merge "^4.2.2"
webpack-dev-middleware@^3.7.0:
version "3.7.2"
@@ -6670,6 +6734,13 @@ webpack-log@^2.0.0:
ansi-colors "^3.0.0"
uuid "^3.3.2"
+webpack-merge@^4.2.2:
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d"
+ integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==
+ dependencies:
+ lodash "^4.17.15"
+
webpack-sources@^1.4.0, webpack-sources@^1.4.1:
version "1.4.3"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"
@@ -6678,10 +6749,10 @@ webpack-sources@^1.4.0, webpack-sources@^1.4.1:
source-list-map "^2.0.0"
source-map "~0.6.1"
-webpack@4.44.1:
- version "4.44.1"
- resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.44.1.tgz#17e69fff9f321b8f117d1fda714edfc0b939cc21"
- integrity sha512-4UOGAohv/VGUNQJstzEywwNxqX417FnjZgZJpJQegddzPmTvph37eBIRbRTfdySXzVtJXLJfbMN3mMYhM6GdmQ==
+webpack@4.44.2:
+ version "4.44.2"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.44.2.tgz#6bfe2b0af055c8b2d1e90ed2cd9363f841266b72"
+ integrity sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q==
dependencies:
"@webassemblyjs/ast" "1.9.0"
"@webassemblyjs/helper-module-context" "1.9.0"
@@ -6719,7 +6790,7 @@ which@2.0.2, which@^2.0.1:
dependencies:
isexe "^2.0.0"
-which@^1.2.14, which@^1.2.9, which@^1.3.1:
+which@^1.2.9:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
@@ -6738,6 +6809,14 @@ word-wrap@^1.2.3, word-wrap@~1.2.3:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
+wordwrapjs@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-4.0.0.tgz#9aa9394155993476e831ba8e59fb5795ebde6800"
+ integrity sha512-Svqw723a3R34KvsMgpjFBYCgNOSdcW3mQFK4wIfhGQhtaFVOJmdYoXgi63ne3dTlWgatVcUc7t4HtQ/+bUVIzQ==
+ dependencies:
+ reduce-flatten "^2.0.0"
+ typical "^5.0.0"
+
worker-farm@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8"
@@ -6852,7 +6931,7 @@ yargs-unparser@1.6.1:
is-plain-obj "^1.1.0"
yargs "^14.2.3"
-yargs@13.3.2, yargs@^13.3.2:
+yargs@13.3.2:
version "13.3.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==