diff options
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); + }); + }); }); @@ -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== |