aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShin'ya Ueoka <ueokande@i-beam.org>2021-07-29 22:29:40 +0900
committerGitHub <noreply@github.com>2021-07-29 22:29:40 +0900
commit5592b02a1500062628063862158116f382f3d8e2 (patch)
tree5c29d29a8fa1aa14f4f6407a66bcaf528c42555c
parent75236e9a41788f64df61b14a99e78aedc548e0ad (diff)
parent1160cf8aedf9810a76d84e3d99a72365e8aeae8a (diff)
Merge pull request #1213 from ueokande/cross-frame-search
Cross frame search
-rw-r--r--e2e/find.test.ts54
-rw-r--r--manifest.json3
-rw-r--r--src/background/Application.ts10
-rw-r--r--src/background/clients/FindClient.ts45
-rw-r--r--src/background/controllers/FindController.ts12
-rw-r--r--src/background/di.ts6
-rw-r--r--src/background/infrastructures/ContentMessageListener.ts22
-rw-r--r--src/background/operators/impls/FindNextOperator.ts94
-rw-r--r--src/background/operators/impls/FindOperatorFactoryChain.ts49
-rw-r--r--src/background/operators/impls/FindPrevOperator.ts94
-rw-r--r--src/background/operators/impls/OperatorFactoryImpl.ts3
-rw-r--r--src/background/presenters/FramePresenter.ts12
-rw-r--r--src/background/repositories/FindRepository.ts59
-rw-r--r--src/background/usecases/FindUseCase.ts15
-rw-r--r--src/background/usecases/StartFindUseCase.ts57
-rw-r--r--src/console/app/hooks.ts11
-rw-r--r--src/content/Application.ts37
-rw-r--r--src/content/MessageListener.ts7
-rw-r--r--src/content/client/FindClient.ts23
-rw-r--r--src/content/client/FindMasterClient.ts27
-rw-r--r--src/content/controllers/FindController.ts13
-rw-r--r--src/content/di.ts10
-rw-r--r--src/content/operators/impls/FindNextOperator.ts15
-rw-r--r--src/content/operators/impls/FindOperatorFactoryChain.ts25
-rw-r--r--src/content/operators/impls/FindPrevOperator.ts15
-rw-r--r--src/content/operators/impls/OperatorFactoryImpl.ts3
-rw-r--r--src/content/presenters/FindPresenter.ts8
-rw-r--r--src/content/repositories/FindRepository.ts17
-rw-r--r--src/content/usecases/FindUseCase.ts63
-rw-r--r--src/shared/messages.ts23
-rw-r--r--test/background/completion/TabCompletionUseCase.test.ts4
-rw-r--r--test/background/mock/MockFindClient.ts23
-rw-r--r--test/background/mock/MockFindRepository.ts31
-rw-r--r--test/background/mock/MockFramePresenter.ts7
-rw-r--r--test/background/mock/MockTabPresenter.ts4
-rw-r--r--test/background/operators/impls/FindNextOperator.test.ts179
-rw-r--r--test/background/operators/impls/FindOperatorFactoryChain.ts23
-rw-r--r--test/background/operators/impls/FindPrevOperator.test.ts179
-rw-r--r--test/background/operators/impls/TabOperatorFactoryChain.test.ts12
-rw-r--r--test/background/repositories/FindRepository.test.ts38
-rw-r--r--test/background/usecases/StartFindUseCase.test.ts180
-rw-r--r--test/content/mock/MockFindMasterClient.ts11
-rw-r--r--test/content/operators/impls/FindNextOperator.test.ts17
-rw-r--r--test/content/operators/impls/FindOperatorFactoryChain.test.ts21
-rw-r--r--test/content/operators/impls/FindPrevOperator.test.ts17
-rw-r--r--test/content/repositories/FindRepository.test.ts14
-rw-r--r--test/content/usecases/FindUseCase.test.ts160
47 files changed, 1199 insertions, 553 deletions
diff --git a/e2e/find.test.ts b/e2e/find.test.ts
index accf37b..dd5069c 100644
--- a/e2e/find.test.ts
+++ b/e2e/find.test.ts
@@ -41,23 +41,31 @@ describe("find test", () => {
await console.execCommand("hello");
await page.switchToTop();
- let selection = await page.getSelection();
- assert.deepStrictEqual(selection, { from: 2, to: 7 });
+ await eventually(async () => {
+ const selection = await page.getSelection();
+ assert.deepStrictEqual(selection, { from: 2, to: 7 });
+ });
// search next keyword
await page.sendKeys("n");
- selection = await page.getSelection();
- assert.deepStrictEqual(selection, { from: 9, to: 14 });
+ await eventually(async () => {
+ const selection = await page.getSelection();
+ assert.deepStrictEqual(selection, { from: 9, to: 14 });
+ });
// search previous keyword
await page.sendKeys(Key.SHIFT, "N");
- selection = await page.getSelection();
- assert.deepStrictEqual(selection, { from: 2, to: 7 });
+ await eventually(async () => {
+ const selection = await page.getSelection();
+ assert.deepStrictEqual(selection, { from: 2, to: 7 });
+ });
// search previous keyword by wrap-search
await page.sendKeys(Key.SHIFT, "N");
- selection = await page.getSelection();
- assert.deepStrictEqual(selection, { from: 16, to: 21 });
+ await eventually(async () => {
+ const selection = await page.getSelection();
+ assert.deepStrictEqual(selection, { from: 16, to: 21 });
+ });
});
it("shows error if pattern not found", async () => {
@@ -66,8 +74,10 @@ describe("find test", () => {
await console.execCommand("world");
await page.switchToTop();
- const selection = await page.getSelection();
- assert.deepStrictEqual(selection, { from: 0, to: 0 });
+ await eventually(async () => {
+ const selection = await page.getSelection();
+ assert.deepStrictEqual(selection, { from: 0, to: 0 });
+ });
await eventually(async () => {
console = await page.getConsole();
@@ -83,16 +93,20 @@ describe("find test", () => {
await page.switchToTop();
await page.clearSelection();
- let selection = await page.getSelection();
- assert.deepStrictEqual(selection, { from: 0, to: 0 });
+ await eventually(async () => {
+ const selection = await page.getSelection();
+ assert.deepStrictEqual(selection, { from: 0, to: 0 });
+ });
await page.sendKeys("/");
console = await page.getConsole();
await console.execCommand("");
await page.switchToTop();
- selection = await page.getSelection();
- assert.deepStrictEqual(selection, { from: 2, to: 7 });
+ await eventually(async () => {
+ const selection = await page.getSelection();
+ assert.deepStrictEqual(selection, { from: 2, to: 7 });
+ });
});
it("search with last keyword on new page", async () => {
@@ -102,12 +116,16 @@ describe("find test", () => {
await page.switchToTop();
await page.sendKeys("n");
- let selection = await page.getSelection();
- assert.deepStrictEqual(selection, { from: 9, to: 14 });
+ await eventually(async () => {
+ const selection = await page.getSelection();
+ assert.deepStrictEqual(selection, { from: 9, to: 14 });
+ });
page = await Page.navigateTo(webdriver, server.url());
await page.sendKeys("n");
- selection = await page.getSelection();
- assert.deepStrictEqual(selection, { from: 2, to: 7 });
+ await eventually(async () => {
+ const selection = await page.getSelection();
+ assert.deepStrictEqual(selection, { from: 2, to: 7 });
+ });
});
});
diff --git a/manifest.json b/manifest.json
index ab432b3..5f73744 100644
--- a/manifest.json
+++ b/manifest.json
@@ -34,7 +34,8 @@
"clipboardRead",
"notifications",
"bookmarks",
- "browserSettings"
+ "browserSettings",
+ "webNavigation"
],
"web_accessible_resources": [
"build/console.html",
diff --git a/src/background/Application.ts b/src/background/Application.ts
index 69fe4a4..2006965 100644
--- a/src/background/Application.ts
+++ b/src/background/Application.ts
@@ -3,6 +3,7 @@ import ContentMessageListener from "./infrastructures/ContentMessageListener";
import SettingController from "./controllers/SettingController";
import VersionController from "./controllers/VersionController";
import SettingRepository from "./repositories/SettingRepository";
+import FindRepositoryImpl from "./repositories/FindRepository";
@injectable()
export default class Application {
@@ -11,12 +12,19 @@ export default class Application {
private settingController: SettingController,
private versionController: VersionController,
@inject("SyncSettingRepository")
- private syncSettingRepository: SettingRepository
+ private syncSettingRepository: SettingRepository,
+ @inject("FindRepository")
+ private readonly findRepository: FindRepositoryImpl
) {}
run() {
this.settingController.reload();
+ browser.tabs.onUpdated.addListener((tabId: number, info) => {
+ if (info.status == "loading") {
+ this.findRepository.deleteLocalState(tabId);
+ }
+ });
browser.runtime.onInstalled.addListener((details) => {
if (details.reason !== "install" && details.reason !== "update") {
return;
diff --git a/src/background/clients/FindClient.ts b/src/background/clients/FindClient.ts
new file mode 100644
index 0000000..b46b964
--- /dev/null
+++ b/src/background/clients/FindClient.ts
@@ -0,0 +1,45 @@
+import * as messages from "../../shared/messages";
+
+export default interface FindClient {
+ findNext(tabId: number, frameId: number, keyword: string): Promise<boolean>;
+
+ findPrev(tabId: number, frameId: number, keyword: string): Promise<boolean>;
+
+ clearSelection(tabId: number, frameId: number): Promise<void>;
+}
+
+export class FindClientImpl implements FindClient {
+ async findNext(
+ tabId: number,
+ frameId: number,
+ keyword: string
+ ): Promise<boolean> {
+ const found = (await browser.tabs.sendMessage(
+ tabId,
+ { type: messages.FIND_NEXT, keyword },
+ { frameId }
+ )) as boolean;
+ return found;
+ }
+
+ async findPrev(
+ tabId: number,
+ frameId: number,
+ keyword: string
+ ): Promise<boolean> {
+ const found = (await browser.tabs.sendMessage(
+ tabId,
+ { type: messages.FIND_PREV, keyword },
+ { frameId }
+ )) as boolean;
+ return found;
+ }
+
+ clearSelection(tabId: number, frameId: number): Promise<void> {
+ return browser.tabs.sendMessage(
+ tabId,
+ { type: messages.FIND_CLEAR_SELECTION },
+ { frameId }
+ );
+ }
+}
diff --git a/src/background/controllers/FindController.ts b/src/background/controllers/FindController.ts
index f3ea93f..0772866 100644
--- a/src/background/controllers/FindController.ts
+++ b/src/background/controllers/FindController.ts
@@ -1,15 +1,11 @@
import { injectable } from "tsyringe";
-import FindUseCase from "../usecases/FindUseCase";
+import StartFindUseCase from "../usecases/StartFindUseCase";
@injectable()
export default class FindController {
- constructor(private findUseCase: FindUseCase) {}
+ constructor(private startFindUseCase: StartFindUseCase) {}
- getKeyword(): Promise<string> {
- return this.findUseCase.getKeyword();
- }
-
- setKeyword(keyword: string): Promise<void> {
- return this.findUseCase.setKeyword(keyword);
+ startFind(tabId: number, keyword?: string): Promise<void> {
+ return this.startFindUseCase.startFind(tabId, keyword);
}
}
diff --git a/src/background/di.ts b/src/background/di.ts
index a571c7d..e97c4a8 100644
--- a/src/background/di.ts
+++ b/src/background/di.ts
@@ -18,7 +18,10 @@ import { BrowserSettingRepositoryImpl } from "./repositories/BrowserSettingRepos
import { RepeatRepositoryImpl } from "./repositories/RepeatRepository";
import { ZoomPresenterImpl } from "./presenters/ZoomPresenter";
import { WindowPresenterImpl } from "./presenters/WindowPresenter";
+import { FramePresenterImpl } from "./presenters/FramePresenter";
+import { FindClientImpl } from "./clients/FindClient";
import { ConsoleFrameClientImpl } from "./clients/ConsoleFrameClient";
+import { FindRepositoryImpl } from "./repositories/FindRepository";
container.register("LocalSettingRepository", {
useClass: LocalSettingRepository,
@@ -40,6 +43,9 @@ container.register("TabRepository", { useClass: TabRepositoryImpl });
container.register("ZoomPresenter", { useClass: ZoomPresenterImpl });
container.register("TabPresenter", { useClass: TabPresenterImpl });
container.register("WindowPresenter", { useClass: WindowPresenterImpl });
+container.register("FramePresenter", { useClass: FramePresenterImpl });
+container.register("FindRepository", { useClass: FindRepositoryImpl });
+container.register("FindClient", { useClass: FindClientImpl });
container.register("NavigateClient", { useClass: NavigateClientImpl });
container.register("ConsoleClient", { useClass: ConsoleClientImpl });
container.register("ConsoleFrameClient", { useClass: ConsoleFrameClientImpl });
diff --git a/src/background/infrastructures/ContentMessageListener.ts b/src/background/infrastructures/ContentMessageListener.ts
index 6023047..ce7ff09 100644
--- a/src/background/infrastructures/ContentMessageListener.ts
+++ b/src/background/infrastructures/ContentMessageListener.ts
@@ -3,13 +3,13 @@ import * as messages from "../../shared/messages";
import * as operations from "../../shared/operations";
import CommandController from "../controllers/CommandController";
import SettingController from "../controllers/SettingController";
-import FindController from "../controllers/FindController";
import AddonEnabledController from "../controllers/AddonEnabledController";
import LinkController from "../controllers/LinkController";
import OperationController from "../controllers/OperationController";
import MarkController from "../controllers/MarkController";
import CompletionController from "../controllers/CompletionController";
import ConsoleController from "../controllers/ConsoleController";
+import FindController from "../controllers/FindController";
@injectable()
export default class ContentMessageListener {
@@ -19,12 +19,12 @@ export default class ContentMessageListener {
private readonly settingController: SettingController,
private readonly commandController: CommandController,
private readonly completionController: CompletionController,
- private readonly findController: FindController,
private readonly addonEnabledController: AddonEnabledController,
private readonly linkController: LinkController,
private readonly operationController: OperationController,
private readonly markController: MarkController,
- private readonly consoleController: ConsoleController
+ private readonly consoleController: ConsoleController,
+ private readonly findController: FindController
) {}
run(): void {
@@ -36,6 +36,7 @@ export default class ContentMessageListener {
return {};
}
return ret.catch((e) => {
+ console.error(e);
if (!sender.tab || !sender.tab.id) {
return;
}
@@ -45,6 +46,7 @@ export default class ContentMessageListener {
});
});
} catch (e) {
+ console.error(e);
if (!sender.tab || !sender.tab.id) {
return;
}
@@ -80,6 +82,8 @@ export default class ContentMessageListener {
return this.completionController.getProperties();
case messages.CONSOLE_ENTER_COMMAND:
return this.onConsoleEnterCommand(message.text);
+ case messages.CONSOLE_ENTER_FIND:
+ return this.findController.startFind(senderTab.id!, message.keyword);
case messages.CONSOLE_RESIZE:
return this.onConsoleResize(
senderTab.id!,
@@ -88,10 +92,6 @@ export default class ContentMessageListener {
);
case messages.SETTINGS_QUERY:
return this.onSettingsQuery();
- case messages.FIND_GET_KEYWORD:
- return this.onFindGetKeyword();
- case messages.FIND_SET_KEYWORD:
- return this.onFindSetKeyword(message.keyword);
case messages.ADDON_ENABLED_RESPONSE:
return this.onAddonEnabledResponse(message.enabled);
case messages.OPEN_URL:
@@ -132,14 +132,6 @@ export default class ContentMessageListener {
return (await this.settingController.getSetting()).toJSON();
}
- onFindGetKeyword(): Promise<string> {
- return this.findController.getKeyword();
- }
-
- onFindSetKeyword(keyword: string): Promise<void> {
- return this.findController.setKeyword(keyword);
- }
-
onAddonEnabledResponse(enabled: boolean): Promise<void> {
return this.addonEnabledController.indicate(enabled);
}
diff --git a/src/background/operators/impls/FindNextOperator.ts b/src/background/operators/impls/FindNextOperator.ts
new file mode 100644
index 0000000..241f71d
--- /dev/null
+++ b/src/background/operators/impls/FindNextOperator.ts
@@ -0,0 +1,94 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+import FindRepository from "../../repositories/FindRepository";
+import FindClient from "../../clients/FindClient";
+import ConsoleClient from "../../infrastructures/ConsoleClient";
+import FramePresenter from "../../presenters/FramePresenter";
+
+export default class FindNextOperator implements Operator {
+ constructor(
+ private readonly tabPresenter: TabPresenter,
+ private readonly findRepository: FindRepository,
+ private readonly findClient: FindClient,
+ private readonly consoleClient: ConsoleClient,
+ private readonly framePresenter: FramePresenter
+ ) {}
+
+ async run(): Promise<void> {
+ const tab = await this.tabPresenter.getCurrent();
+ const tabId = tab?.id;
+ if (tabId == null) {
+ return;
+ }
+
+ const state = await this.findRepository.getLocalState(tabId);
+ if (state) {
+ // Start to find the keyword from the current frame which last found on,
+ // and concat it to end of frame ids to perform a wrap-search
+ //
+ // ,- keyword should be in this frame
+ // |
+ // [100, 101, 0, 100]
+ // |
+ // `- continue from frame id 100
+ //
+ const targetFrameIds = state.frameIds
+ .slice(state.framePos)
+ .concat(
+ state.frameIds.slice(0, state.framePos),
+ state.frameIds[state.framePos]
+ );
+
+ for (let i = 0; i < targetFrameIds.length; ++i) {
+ const found = await this.findClient.findNext(
+ tabId,
+ targetFrameIds[i],
+ state.keyword
+ );
+ if (found) {
+ this.findRepository.setLocalState(tabId, {
+ ...state,
+ framePos: (i + state.framePos) % state.frameIds.length, // save current frame position or first
+ });
+ return;
+ }
+ this.findClient.clearSelection(tabId, targetFrameIds[i]);
+ }
+
+ // The keyword is gone.
+ this.consoleClient.showError(
+ tabId,
+ "Pattern not found: " + state.keyword
+ );
+ return;
+ }
+
+ const keyword = await this.findRepository.getGlobalKeyword();
+ if (keyword) {
+ const frameIds = await this.framePresenter.getAllFrameIds(tabId);
+ for (const frameId of frameIds) {
+ await this.findClient.clearSelection(tabId, frameId);
+ }
+
+ for (let framePos = 0; framePos < frameIds.length; ++framePos) {
+ const found = await this.findClient.findNext(
+ tabId,
+ frameIds[framePos],
+ keyword
+ );
+ if (found) {
+ await this.findRepository.setLocalState(tabId, {
+ frameIds,
+ framePos,
+ keyword,
+ });
+ await this.consoleClient.showInfo(tabId, "Pattern found: " + keyword);
+ return;
+ }
+ }
+ this.consoleClient.showError(tabId, "Pattern not found: " + keyword);
+ return;
+ }
+ await this.consoleClient.showError(tabId, "No previous search keywords");
+ }
+}
diff --git a/src/background/operators/impls/FindOperatorFactoryChain.ts b/src/background/operators/impls/FindOperatorFactoryChain.ts
new file mode 100644
index 0000000..b71f032
--- /dev/null
+++ b/src/background/operators/impls/FindOperatorFactoryChain.ts
@@ -0,0 +1,49 @@
+import { inject, injectable } from "tsyringe";
+import Operator from "../Operator";
+import OperatorFactoryChain from "../OperatorFactoryChain";
+import TabPresenter from "../../presenters/TabPresenter";
+import * as operations from "../../../shared/operations";
+import FindNextOperator from "./FindNextOperator";
+import FindPrevOperator from "./FindPrevOperator";
+import FindRepository from "../../repositories/FindRepository";
+import FindClient from "../../clients/FindClient";
+import FramePresenter from "../../presenters/FramePresenter";
+import ConsoleClient from "../../infrastructures/ConsoleClient";
+
+@injectable()
+export default class FindOperatorFactoryChain implements OperatorFactoryChain {
+ constructor(
+ @inject("TabPresenter")
+ private readonly tabPresenter: TabPresenter,
+ @inject("FindRepository")
+ private readonly findRepository: FindRepository,
+ @inject("FindClient")
+ private readonly findClient: FindClient,
+ @inject("ConsoleClient")
+ private readonly consoleClient: ConsoleClient,
+ @inject("FramePresenter")
+ private readonly framePresenter: FramePresenter
+ ) {}
+
+ create(op: operations.Operation): Operator | null {
+ switch (op.type) {
+ case operations.FIND_NEXT:
+ return new FindNextOperator(
+ this.tabPresenter,
+ this.findRepository,
+ this.findClient,
+ this.consoleClient,
+ this.framePresenter
+ );
+ case operations.FIND_PREV:
+ return new FindPrevOperator(
+ this.tabPresenter,
+ this.findRepository,
+ this.findClient,
+ this.consoleClient,
+ this.framePresenter
+ );
+ }
+ return null;
+ }
+}
diff --git a/src/background/operators/impls/FindPrevOperator.ts b/src/background/operators/impls/FindPrevOperator.ts
new file mode 100644
index 0000000..822c386
--- /dev/null
+++ b/src/background/operators/impls/FindPrevOperator.ts
@@ -0,0 +1,94 @@
+import Operator from "../Operator";
+import TabPresenter from "../../presenters/TabPresenter";
+import FindRepository from "../../repositories/FindRepository";
+import FindClient from "../../clients/FindClient";
+import ConsoleClient from "../../infrastructures/ConsoleClient";
+import FramePresenter from "../../presenters/FramePresenter";
+
+export default class FindPrevOperator implements Operator {
+ constructor(
+ private readonly tabPresenter: TabPresenter,
+ private readonly findRepository: FindRepository,
+ private readonly findClient: FindClient,
+ private readonly consoleClient: ConsoleClient,
+ private readonly framePresenter: FramePresenter
+ ) {}
+
+ async run(): Promise<void> {
+ const tab = await this.tabPresenter.getCurrent();
+ const tabId = tab?.id;
+ if (tabId == null) {
+ return;
+ }
+
+ const state = await this.findRepository.getLocalState(tabId);
+ if (state) {
+ // Start to find the keyword from the current frame which last found on,
+ // and concat it to end of frame ids to perform a wrap-search
+ //
+ // ,- keyword should be in this frame
+ // |
+ // [100, 101, 0, 100]
+ // |
+ // `- continue from frame id 100
+ //
+ const targetFrameIds = state.frameIds
+ .slice(state.framePos)
+ .concat(
+ state.frameIds.slice(0, state.framePos),
+ state.frameIds[state.framePos]
+ );
+
+ for (let i = targetFrameIds.length - 1; i >= 0; --i) {
+ const found = await this.findClient.findPrev(
+ tabId,
+ targetFrameIds[i],
+ state.keyword
+ );
+ if (found) {
+ this.findRepository.setLocalState(tabId, {
+ ...state,
+ framePos: (i + state.framePos) % state.frameIds.length, // save current frame position or first
+ });
+ return;
+ }
+ this.findClient.clearSelection(tabId, targetFrameIds[i]);
+ }
+
+ // The keyword is gone.
+ this.consoleClient.showError(
+ tabId,
+ "Pattern not found: " + state.keyword
+ );
+ return;
+ }
+
+ const keyword = await this.findRepository.getGlobalKeyword();
+ if (keyword) {
+ const frameIds = await this.framePresenter.getAllFrameIds(tabId);
+ for (const frameId of frameIds) {
+ await this.findClient.clearSelection(tabId, frameId);
+ }
+
+ for (let framePos = frameIds.length - 1; framePos >= 0; --framePos) {
+ const found = await this.findClient.findPrev(
+ tabId,
+ frameIds[framePos],
+ keyword
+ );
+ if (found) {
+ await this.findRepository.setLocalState(tabId, {
+ frameIds,
+ framePos,
+ keyword,
+ });
+ await this.consoleClient.showInfo(tabId, "Pattern found: " + keyword);
+ return;
+ }
+ }
+ this.consoleClient.showError(tabId, "Pattern not found: " + keyword);
+ return;
+ }
+ await this.consoleClient.showError(tabId, "No previous search keywords");
+ }
+}
diff --git a/src/background/operators/impls/OperatorFactoryImpl.ts b/src/background/operators/impls/OperatorFactoryImpl.ts
index 34e7bb5..ce87491 100644
--- a/src/background/operators/impls/OperatorFactoryImpl.ts
+++ b/src/background/operators/impls/OperatorFactoryImpl.ts
@@ -8,6 +8,7 @@ import NavigateOperatorFactoryChain from "./NavigateOperatorFactoryChain";
import RepeatOperatorFactoryChain from "./RepeatOperatorFactoryChain";
import TabOperatorFactoryChain from "./TabOperatorFactoryChain";
import ZoomOperatorFactoryChain from "./ZoomOperatorFactoryChain";
+import FindOperatorFactoryChain from "./FindOperatorFactoryChain";
import * as operations from "../../../shared/operations";
@injectable()
@@ -20,6 +21,7 @@ export class OperatorFactoryImpl implements OperatorFactory {
navigateOperatorFactoryChain: NavigateOperatorFactoryChain,
tabOperatorFactoryChain: TabOperatorFactoryChain,
zoomOperatorFactoryChain: ZoomOperatorFactoryChain,
+ findOperatorFactoryChain: FindOperatorFactoryChain,
@inject(delay(() => RepeatOperatorFactoryChain))
repeatOperatorFactoryChain: RepeatOperatorFactoryChain
) {
@@ -30,6 +32,7 @@ export class OperatorFactoryImpl implements OperatorFactory {
repeatOperatorFactoryChain,
tabOperatorFactoryChain,
zoomOperatorFactoryChain,
+ findOperatorFactoryChain,
];
}
diff --git a/src/background/presenters/FramePresenter.ts b/src/background/presenters/FramePresenter.ts
new file mode 100644
index 0000000..c94f8dd
--- /dev/null
+++ b/src/background/presenters/FramePresenter.ts
@@ -0,0 +1,12 @@
+export default interface FramePresenter {
+ getAllFrameIds(tabId: number): Promise<Array<number>>;
+}
+
+export class FramePresenterImpl implements FramePresenter {
+ async getAllFrameIds(tabId: number): Promise<Array<number>> {
+ const frames = await browser.webNavigation.getAllFrames({ tabId: tabId });
+ return frames
+ .filter((f) => !f.url.startsWith("moz-extension://"))
+ .map((f) => f.frameId);
+ }
+}
diff --git a/src/background/repositories/FindRepository.ts b/src/background/repositories/FindRepository.ts
index 813e065..46ee390 100644
--- a/src/background/repositories/FindRepository.ts
+++ b/src/background/repositories/FindRepository.ts
@@ -1,22 +1,69 @@
import { injectable } from "tsyringe";
import MemoryStorage from "../infrastructures/MemoryStorage";
-const FIND_KEYWORD_KEY = "find-keyword";
+const FIND_GLOBAL_KEYWORD_KEY = "find-global-keyword";
+const FIND_LOCAL_KEYWORD_KEY = "find-local-keyword";
+
+export type FindState = {
+ keyword: string;
+ framePos: number;
+ frameIds: number[];
+};
+
+export default interface FindRepository {
+ getGlobalKeyword(): Promise<string | undefined>;
+
+ setGlobalKeyword(keyword: string): Promise<void>;
+
+ getLocalState(tabId: number): Promise<undefined | FindState>;
+
+ setLocalState(tabId: number, state: FindState): Promise<void>;
+
+ deleteLocalState(tabId: number): Promise<void>;
+}
@injectable()
-export default class FindRepository {
+export class FindRepositoryImpl implements FindRepository {
private cache: MemoryStorage;
constructor() {
this.cache = new MemoryStorage();
}
- getKeyword(): Promise<string> {
- return Promise.resolve(this.cache.get(FIND_KEYWORD_KEY));
+ getGlobalKeyword(): Promise<string | undefined> {
+ return Promise.resolve(this.cache.get(FIND_GLOBAL_KEYWORD_KEY));
+ }
+
+ setGlobalKeyword(keyword: string): Promise<void> {
+ this.cache.set(FIND_GLOBAL_KEYWORD_KEY, keyword);
+ return Promise.resolve();
+ }
+
+ getLocalState(tabId: number): Promise<FindState | undefined> {
+ let states = this.cache.get(FIND_LOCAL_KEYWORD_KEY);
+ if (typeof states === "undefined") {
+ states = {};
+ }
+ return Promise.resolve(states[tabId]);
+ }
+
+ setLocalState(tabId: number, state: FindState): Promise<void> {
+ let states = this.cache.get(FIND_LOCAL_KEYWORD_KEY);
+ if (typeof states === "undefined") {
+ states = {};
+ }
+ states[tabId] = state;
+ this.cache.set(FIND_LOCAL_KEYWORD_KEY, states);
+ return Promise.resolve();
}
- setKeyword(keyword: string): Promise<void> {
- this.cache.set(FIND_KEYWORD_KEY, keyword);
+ deleteLocalState(tabId: number): Promise<void> {
+ const states = this.cache.get(FIND_LOCAL_KEYWORD_KEY);
+ if (typeof states === "undefined") {
+ return Promise.resolve();
+ }
+ delete states[tabId];
+ this.cache.set(FIND_LOCAL_KEYWORD_KEY, states);
return Promise.resolve();
}
}
diff --git a/src/background/usecases/FindUseCase.ts b/src/background/usecases/FindUseCase.ts
deleted file mode 100644
index cc111f2..0000000
--- a/src/background/usecases/FindUseCase.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { injectable } from "tsyringe";
-import FindRepository from "../repositories/FindRepository";
-
-@injectable()
-export default class FindUseCase {
- constructor(private readonly findRepository: FindRepository) {}
-
- getKeyword(): Promise<string> {
- return this.findRepository.getKeyword();
- }
-
- setKeyword(keyword: string): Promise<void> {
- return this.findRepository.setKeyword(keyword);
- }
-}
diff --git a/src/background/usecases/StartFindUseCase.ts b/src/background/usecases/StartFindUseCase.ts
new file mode 100644
index 0000000..066d930
--- /dev/null
+++ b/src/background/usecases/StartFindUseCase.ts
@@ -0,0 +1,57 @@
+import { inject, injectable } from "tsyringe";
+import ConsoleClient from "../infrastructures/ConsoleClient";
+import FindRepositoryImpl from "../repositories/FindRepository";
+import FindClient from "../clients/FindClient";
+import FramePresenter from "../presenters/FramePresenter";
+
+@injectable()
+export default class StartFindUseCase {
+ constructor(
+ @inject("FindClient")
+ private readonly findClient: FindClient,
+ @inject("FindRepository")
+ private readonly findRepository: FindRepositoryImpl,
+ @inject("ConsoleClient")
+ private readonly consoleClient: ConsoleClient,
+ @inject("FramePresenter")
+ private readonly framePresenter: FramePresenter
+ ) {}
+
+ async startFind(tabId: number, keyword?: string): Promise<void> {
+ if (typeof keyword === "undefined") {
+ keyword = (await this.findRepository.getLocalState(tabId))?.keyword;
+ }
+ if (typeof keyword === "undefined") {
+ keyword = await this.findRepository.getGlobalKeyword();
+ }
+ if (typeof keyword === "undefined") {
+ await this.consoleClient.showError(tabId, "No previous search keywords");
+ return;
+ }
+
+ this.findRepository.setGlobalKeyword(keyword);
+
+ const frameIds = await this.framePresenter.getAllFrameIds(tabId);
+ for (const frameId of frameIds) {
+ await this.findClient.clearSelection(tabId, frameId);
+ }
+
+ for (let framePos = 0; framePos < frameIds.length; ++framePos) {
+ const found = await this.findClient.findNext(
+ tabId,
+ frameIds[framePos],
+ keyword
+ );
+ if (found) {
+ await this.findRepository.setLocalState(tabId, {
+ frameIds,
+ framePos,
+ keyword,
+ });
+ await this.consoleClient.showInfo(tabId, "Pattern found: " + keyword);
+ return;
+ }
+ }
+ this.consoleClient.showError(tabId, "Pattern not found: " + keyword);
+ }
+}
diff --git a/src/console/app/hooks.ts b/src/console/app/hooks.ts
index eefdea3..00ac05c 100644
--- a/src/console/app/hooks.ts
+++ b/src/console/app/hooks.ts
@@ -103,13 +103,10 @@ export const useExecCommand = () => {
export const useExecFind = () => {
const execFind = React.useCallback((text?: string) => {
- window.top.postMessage(
- JSON.stringify({
- type: messages.CONSOLE_ENTER_FIND,
- text,
- }),
- "*"
- );
+ browser.runtime.sendMessage({
+ type: messages.CONSOLE_ENTER_FIND,
+ keyword: text,
+ });
}, []);
return execFind;
};
diff --git a/src/content/Application.ts b/src/content/Application.ts
index b09edfa..a12c3c6 100644
--- a/src/content/Application.ts
+++ b/src/content/Application.ts
@@ -1,6 +1,5 @@
import { injectable } from "tsyringe";
import MessageListener from "./MessageListener";
-import FindController from "./controllers/FindController";
import MarkController from "./controllers/MarkController";
import FollowMasterController from "./controllers/FollowMasterController";
import FollowSlaveController from "./controllers/FollowSlaveController";
@@ -14,6 +13,7 @@ import SettingController from "./controllers/SettingController";
import ConsoleFrameController from "./controllers/ConsoleFrameController";
import NavigateController from "./controllers/NavigateController";
import * as messages from "../shared/messages";
+import FindController from "./controllers/FindController";
type Message = messages.Message;
@@ -22,7 +22,6 @@ export default class Application {
// eslint-disable-next-line max-params
constructor(
private messageListener: MessageListener,
- private findController: FindController,
private markController: MarkController,
private followMasterController: FollowMasterController,
private followSlaveController: FollowSlaveController,
@@ -33,7 +32,8 @@ export default class Application {
private addonEnabledController: AddonEnabledController,
private settingController: SettingController,
private consoleFrameController: ConsoleFrameController,
- private navigateController: NavigateController
+ private navigateController: NavigateController,
+ private findController: FindController
) {}
init(): Promise<void> {
@@ -47,12 +47,6 @@ export default class Application {
private routeMasterComponents() {
this.messageListener.onWebMessage((msg: Message, sender: Window) => {
switch (msg.type) {
- case messages.CONSOLE_ENTER_FIND:
- return this.findController.start(msg);
- case messages.FIND_NEXT:
- return this.findController.next(msg);
- case messages.FIND_PREV:
- return this.findController.prev(msg);
case messages.CONSOLE_UNFOCUS:
return this.consoleFrameController.unfocus(msg);
case messages.FOLLOW_START:
@@ -64,16 +58,6 @@ export default class Application {
}
return undefined;
});
-
- this.messageListener.onBackgroundMessage((msg: Message) => {
- switch (msg.type) {
- case messages.ADDON_ENABLED_QUERY:
- return this.addonEnabledController.getAddonEnabled(msg);
- case messages.TAB_SCROLL_TO:
- return this.markController.scrollTo(msg);
- }
- return undefined;
- });
}
private routeCommonComponents(): Promise<void> {
@@ -109,6 +93,21 @@ export default class Application {
return this.navigateController.openLinkPrev(msg);
case messages.CONSOLE_RESIZE:
return this.consoleFrameController.resize(msg);
+ case messages.FIND_NEXT:
+ return this.findController.findNext(msg.keyword);
+ case messages.FIND_PREV:
+ return this.findController.findPrev(msg.keyword);
+ case messages.FIND_CLEAR_SELECTION:
+ return this.findController.clearSelection();
+ }
+
+ if (window.self === window.top) {
+ switch (msg.type) {
+ case messages.ADDON_ENABLED_QUERY:
+ return this.addonEnabledController.getAddonEnabled(msg);
+ case messages.TAB_SCROLL_TO:
+ return this.markController.scrollTo(msg);
+ }
}
});
diff --git a/src/content/MessageListener.ts b/src/content/MessageListener.ts
index 3fe1dcd..31cc1ae 100644
--- a/src/content/MessageListener.ts
+++ b/src/content/MessageListener.ts
@@ -27,7 +27,12 @@ export default class MessageListener {
) {
browser.runtime.onMessage.addListener(
(msg: any, sender: WebExtMessageSender) => {
- return listener(valueOf(msg), sender);
+ try {
+ return Promise.resolve(listener(valueOf(msg), sender));
+ } catch (e) {
+ console.warn(e);
+ return;
+ }
}
);
}
diff --git a/src/content/client/FindClient.ts b/src/content/client/FindClient.ts
deleted file mode 100644
index 7da5069..0000000
--- a/src/content/client/FindClient.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import * as messages from "../../shared/messages";
-
-export default interface FindClient {
- getGlobalLastKeyword(): Promise<string | null>;
-
- setGlobalLastKeyword(keyword: string): Promise<void>;
-}
-
-export class FindClientImpl implements FindClient {
- async getGlobalLastKeyword(): Promise<string | null> {
- const keyword = await browser.runtime.sendMessage({
- type: messages.FIND_GET_KEYWORD,
- });
- return keyword as string;
- }
-
- async setGlobalLastKeyword(keyword: string): Promise<void> {
- await browser.runtime.sendMessage({
- type: messages.FIND_SET_KEYWORD,
- keyword: keyword,
- });
- }
-}
diff --git a/src/content/client/FindMasterClient.ts b/src/content/client/FindMasterClient.ts
deleted file mode 100644
index 9c3f812..0000000
--- a/src/content/client/FindMasterClient.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import * as messages from "../../shared/messages";
-
-export default interface FindMasterClient {
- findNext(): void;
-
- findPrev(): void;
-}
-
-export class FindMasterClientImpl implements FindMasterClient {
- findNext(): void {
- window.top.postMessage(
- JSON.stringify({
- type: messages.FIND_NEXT,
- }),
- "*"
- );
- }
-
- findPrev(): void {
- window.top.postMessage(
- JSON.stringify({
- type: messages.FIND_PREV,
- }),
- "*"
- );
- }
-}
diff --git a/src/content/controllers/FindController.ts b/src/content/controllers/FindController.ts
index 3087d5d..adcdb0d 100644
--- a/src/content/controllers/FindController.ts
+++ b/src/content/controllers/FindController.ts
@@ -1,20 +1,19 @@
import { injectable } from "tsyringe";
-import * as messages from "../../shared/messages";
import FindUseCase from "../usecases/FindUseCase";
@injectable()
export default class FindController {
constructor(private findUseCase: FindUseCase) {}
- async start(m: messages.ConsoleEnterFindMessage): Promise<void> {
- await this.findUseCase.startFind(m.text);
+ findNext(keyword: string): boolean {
+ return this.findUseCase.findNext(keyword);
}
- async next(_: messages.FindNextMessage): Promise<void> {
- await this.findUseCase.findNext();
+ findPrev(keyword: string): boolean {
+ return this.findUseCase.findPrev(keyword);
}
- async prev(_: messages.FindPrevMessage): Promise<void> {
- await this.findUseCase.findPrev();
+ clearSelection() {
+ return this.findUseCase.clearSelection();
}
}
diff --git a/src/content/di.ts b/src/content/di.ts
index e74d7ac..4c85e76 100644
--- a/src/content/di.ts
+++ b/src/content/di.ts
@@ -6,10 +6,6 @@ import { AddressRepositoryImpl } from "./repositories/AddressRepository";
import { ClipboardRepositoryImpl } from "./repositories/ClipboardRepository";
import { ConsoleClientImpl } from "./client/ConsoleClient";
import { ConsoleFramePresenterImpl } from "./presenters/ConsoleFramePresenter";
-import { FindClientImpl } from "./client/FindClient";
-import { FindMasterClientImpl } from "./client/FindMasterClient";
-import { FindPresenterImpl } from "./presenters/FindPresenter";
-import { FindRepositoryImpl } from "./repositories/FindRepository";
import { FocusPresenterImpl } from "./presenters/FocusPresenter";
import { FollowKeyRepositoryImpl } from "./repositories/FollowKeyRepository";
import { FollowMasterClientImpl } from "./client/FollowMasterClient";
@@ -31,6 +27,7 @@ import { TabsClientImpl } from "./client/TabsClient";
import { container } from "tsyringe";
import OperatorFactoryImpl from "./operators/impls/OperatorFactoryImpl";
import { URLRepositoryImpl } from "./operators/impls/URLRepository";
+import { FindPresenterImpl } from "./presenters/FindPresenter";
container.register("FollowMasterClient", {
useValue: new FollowMasterClientImpl(window.top),
@@ -49,10 +46,6 @@ container.register("ConsoleClient", { useClass: ConsoleClientImpl });
container.register("ConsoleFramePresenter", {
useClass: ConsoleFramePresenterImpl,
});
-container.register("FindClient", { useClass: FindClientImpl });
-container.register("FindMasterClient", { useClass: FindMasterClientImpl });
-container.register("FindPresenter", { useClass: FindPresenterImpl });
-container.register("FindRepository", { useClass: FindRepositoryImpl });
container.register("FocusPresenter", { useClass: FocusPresenterImpl });
container.register("FollowKeyRepository", {
useClass: FollowKeyRepositoryImpl,
@@ -79,6 +72,7 @@ container.register("NavigationPresenter", {
});
container.register("OperationClient", { useClass: OperationClientImpl });
container.register("ScrollPresenter", { useClass: ScrollPresenterImpl });
+container.register("FindPresenter", { useClass: FindPresenterImpl });
container.register("SettingClient", { useClass: SettingClientImpl });
container.register("SettingRepository", { useClass: SettingRepositoryImpl });
container.register("URLRepository", { useClass: URLRepositoryImpl });
diff --git a/src/content/operators/impls/FindNextOperator.ts b/src/content/operators/impls/FindNextOperator.ts
deleted file mode 100644
index c67f6d9..0000000
--- a/src/content/operators/impls/FindNextOperator.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-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
deleted file mode 100644
index b3524c1..0000000
--- a/src/content/operators/impls/FindOperatorFactoryChain.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-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
deleted file mode 100644
index f73e605..0000000
--- a/src/content/operators/impls/FindPrevOperator.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-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/OperatorFactoryImpl.ts b/src/content/operators/impls/OperatorFactoryImpl.ts
index 22b35c8..bc9bbee 100644
--- a/src/content/operators/impls/OperatorFactoryImpl.ts
+++ b/src/content/operators/impls/OperatorFactoryImpl.ts
@@ -7,7 +7,6 @@ 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";
@@ -20,7 +19,6 @@ export default class OperatorFactoryImpl implements OperatorFactory {
constructor(
addonOperatorFactoryChain: AddonOperatorFactoryChain,
clipboardOperatorFactoryChain: ClipboardOperatorFactoryChain,
- findOperatorFactoryChain: FindOperatorFactoryChain,
focusOperatorFactoryChain: FocusOperatorFactoryChain,
followOperatorFactoryChain: FollowOperatorFactoryChain,
markOperatorFactoryChain: MarkOperatorFactoryChain,
@@ -31,7 +29,6 @@ export default class OperatorFactoryImpl implements OperatorFactory {
this.factoryChains = [
addonOperatorFactoryChain,
clipboardOperatorFactoryChain,
- findOperatorFactoryChain,
focusOperatorFactoryChain,
followOperatorFactoryChain,
markOperatorFactoryChain,
diff --git a/src/content/presenters/FindPresenter.ts b/src/content/presenters/FindPresenter.ts
index b25190c..569f161 100644
--- a/src/content/presenters/FindPresenter.ts
+++ b/src/content/presenters/FindPresenter.ts
@@ -7,16 +7,10 @@ export default interface FindPresenter {
export class FindPresenterImpl implements FindPresenter {
find(keyword: string, backwards: boolean): boolean {
const caseSensitive = false;
- const wrapScan = true;
+ const wrapScan = false;
// NOTE: aWholeWord dows not implemented, and aSearchInFrames does not work
// because of same origin policy
- const found = window.find(keyword, caseSensitive, backwards, wrapScan);
- if (found) {
- return found;
- }
- this.clearSelection();
-
return window.find(keyword, caseSensitive, backwards, wrapScan);
}
diff --git a/src/content/repositories/FindRepository.ts b/src/content/repositories/FindRepository.ts
deleted file mode 100644
index aeb200f..0000000
--- a/src/content/repositories/FindRepository.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-export default interface FindRepository {
- getLastKeyword(): string | null;
-
- setLastKeyword(keyword: string): void;
-}
-
-let current: string | null = null;
-
-export class FindRepositoryImpl implements FindRepository {
- getLastKeyword(): string | null {
- return current;
- }
-
- setLastKeyword(keyword: string): void {
- current = keyword;
- }
-}
diff --git a/src/content/usecases/FindUseCase.ts b/src/content/usecases/FindUseCase.ts
index bff0eee..9e77124 100644
--- a/src/content/usecases/FindUseCase.ts
+++ b/src/content/usecases/FindUseCase.ts
@@ -1,67 +1,22 @@
-import { injectable, inject } from "tsyringe";
+import { inject, injectable } from "tsyringe";
import FindPresenter from "../presenters/FindPresenter";
-import FindRepository from "../repositories/FindRepository";
-import FindClient from "../client/FindClient";
-import ConsoleClient from "../client/ConsoleClient";
@injectable()
export default class FindUseCase {
constructor(
- @inject("FindPresenter") private presenter: FindPresenter,
- @inject("FindRepository") private repository: FindRepository,
- @inject("FindClient") private client: FindClient,
- @inject("ConsoleClient") private consoleClient: ConsoleClient
+ @inject("FindPresenter")
+ private readonly findPresenter: FindPresenter
) {}
- async startFind(keyword?: string): Promise<void> {
- this.presenter.clearSelection();
- if (keyword) {
- this.saveKeyword(keyword);
- } else {
- const lastKeyword = await this.getKeyword();
- if (!lastKeyword) {
- return this.showNoLastKeywordError();
- }
- this.saveKeyword(lastKeyword);
- }
- return this.findNext();
+ findNext(keyword: string): boolean {
+ return this.findPresenter.find(keyword, false);
}
- findNext(): Promise<void> {
- return this.findNextPrev(false);
+ findPrev(keyword: string): boolean {
+ return this.findPresenter.find(keyword, true);
}
- findPrev(): Promise<void> {
- return this.findNextPrev(true);
- }
-
- private async findNextPrev(backwards: boolean): Promise<void> {
- const keyword = await this.getKeyword();
- if (!keyword) {
- return this.showNoLastKeywordError();
- }
- const found = this.presenter.find(keyword, backwards);
- if (found) {
- this.consoleClient.info("Pattern found: " + keyword);
- } else {
- this.consoleClient.error("Pattern not found: " + keyword);
- }
- }
-
- private async getKeyword(): Promise<string | null> {
- let keyword = this.repository.getLastKeyword();
- if (!keyword) {
- keyword = await this.client.getGlobalLastKeyword();
- }
- return keyword;
- }
-
- private async saveKeyword(keyword: string): Promise<void> {
- this.repository.setLastKeyword(keyword);
- await this.client.setGlobalLastKeyword(keyword);
- }
-
- private async showNoLastKeywordError(): Promise<void> {
- await this.consoleClient.error("No previous search keywords");
+ clearSelection() {
+ this.findPresenter.clearSelection();
}
}
diff --git a/src/shared/messages.ts b/src/shared/messages.ts
index 59329e7..8b9e598 100644
--- a/src/shared/messages.ts
+++ b/src/shared/messages.ts
@@ -37,8 +37,7 @@ export const TAB_SCROLL_TO = "tab.scroll.to";
export const FIND_NEXT = "find.next";
export const FIND_PREV = "find.prev";
-export const FIND_GET_KEYWORD = "find.get.keyword";
-export const FIND_SET_KEYWORD = "find.set.keyword";
+export const FIND_CLEAR_SELECTION = "find.clear.selection";
export const ADDON_ENABLED_QUERY = "addon.enabled.query";
export const ADDON_ENABLED_RESPONSE = "addon.enabled.response";
@@ -73,7 +72,7 @@ export interface ConsoleEnterCommandMessage {
export interface ConsoleEnterFindMessage {
type: typeof CONSOLE_ENTER_FIND;
- text?: string;
+ keyword?: string;
}
export interface ConsoleShowCommandMessage {
@@ -229,20 +228,16 @@ export interface TabScrollToMessage {
export interface FindNextMessage {
type: typeof FIND_NEXT;
+ keyword: string;
}
export interface FindPrevMessage {
type: typeof FIND_PREV;
+ keyword: string;
}
-export interface FindGetKeywordMessage {
- type: typeof FIND_GET_KEYWORD;
-}
-
-export interface FindSetKeywordMessage {
- type: typeof FIND_SET_KEYWORD;
- keyword: string;
- found: boolean;
+export interface FindClearSelection {
+ type: typeof FIND_CLEAR_SELECTION;
}
export interface AddonEnabledQueryMessage {
@@ -324,8 +319,7 @@ export type Message =
| TabScrollToMessage
| FindNextMessage
| FindPrevMessage
- | FindGetKeywordMessage
- | FindSetKeywordMessage
+ | FindClearSelection
| AddonEnabledQueryMessage
| AddonEnabledResponseMessage
| AddonToggleEnabledMessage
@@ -362,8 +356,7 @@ export const valueOf = (o: any): Message => {
case TAB_SCROLL_TO:
case FIND_NEXT:
case FIND_PREV:
- case FIND_GET_KEYWORD:
- case FIND_SET_KEYWORD:
+ case FIND_CLEAR_SELECTION:
case ADDON_ENABLED_QUERY:
case ADDON_ENABLED_RESPONSE:
case ADDON_TOGGLE_ENABLED:
diff --git a/test/background/completion/TabCompletionUseCase.test.ts b/test/background/completion/TabCompletionUseCase.test.ts
index e1a88a2..319f217 100644
--- a/test/background/completion/TabCompletionUseCase.test.ts
+++ b/test/background/completion/TabCompletionUseCase.test.ts
@@ -82,6 +82,10 @@ class MockTabPresenter implements TabPresenter {
setZoom(_tabId: number, _factor: number): Promise<void> {
throw new Error("not implemented");
}
+
+ toggleReaderMode(_tabId: number): Promise<void> {
+ throw new Error("not implemented");
+ }
}
describe("TabCompletionUseCase", () => {
diff --git a/test/background/mock/MockFindClient.ts b/test/background/mock/MockFindClient.ts
new file mode 100644
index 0000000..dd6d8f3
--- /dev/null
+++ b/test/background/mock/MockFindClient.ts
@@ -0,0 +1,23 @@
+import FindClient from "../../../src/background/clients/FindClient";
+
+export default class MockFindClient implements FindClient {
+ findNext(
+ _tabId: number,
+ _frameId: number,
+ _keyword: string
+ ): Promise<boolean> {
+ throw new Error("not implemented");
+ }
+
+ findPrev(
+ _tabId: number,
+ _frameId: number,
+ _keyword: string
+ ): Promise<boolean> {
+ throw new Error("not implemented");
+ }
+
+ clearSelection(_tabId: number, _frameId: number): Promise<void> {
+ throw new Error("not implemented");
+ }
+}
diff --git a/test/background/mock/MockFindRepository.ts b/test/background/mock/MockFindRepository.ts
new file mode 100644
index 0000000..d5151f8
--- /dev/null
+++ b/test/background/mock/MockFindRepository.ts
@@ -0,0 +1,31 @@
+import FindRepository, {
+ FindState,
+} from "../../../src/background/repositories/FindRepository";
+
+export default class MockFindRepository implements FindRepository {
+ private globalKeyword: string | undefined;
+ private localStates: { [tabId: number]: FindState } = {};
+
+ getGlobalKeyword(): Promise<string | undefined> {
+ return Promise.resolve(this.globalKeyword);
+ }
+
+ setGlobalKeyword(keyword: string): Promise<void> {
+ this.globalKeyword = keyword;
+ return Promise.resolve();
+ }
+
+ getLocalState(tabId: number): Promise<FindState | undefined> {
+ return Promise.resolve(this.localStates[tabId]);
+ }
+
+ setLocalState(tabId: number, state: FindState): Promise<void> {
+ this.localStates[tabId] = state;
+ return Promise.resolve();
+ }
+
+ deleteLocalState(tabId: number): Promise<void> {
+ delete this.localStates[tabId];
+ return Promise.resolve();
+ }
+}
diff --git a/test/background/mock/MockFramePresenter.ts b/test/background/mock/MockFramePresenter.ts
new file mode 100644
index 0000000..d688780
--- /dev/null
+++ b/test/background/mock/MockFramePresenter.ts
@@ -0,0 +1,7 @@
+import FramePresenter from "../../../src/background/presenters/FramePresenter";
+
+export default class MockFramePresenter implements FramePresenter {
+ getAllFrameIds(): Promise<number[]> {
+ throw new Error("not implemented");
+ }
+}
diff --git a/test/background/mock/MockTabPresenter.ts b/test/background/mock/MockTabPresenter.ts
index 22fb947..0968e44 100644
--- a/test/background/mock/MockTabPresenter.ts
+++ b/test/background/mock/MockTabPresenter.ts
@@ -176,4 +176,8 @@ export default class MockTabPresenter implements TabPresenter {
this.zooms[index] = factor;
return Promise.resolve();
}
+
+ toggleReaderMode(_tabId: number): Promise<void> {
+ throw new Error("not implemented");
+ }
}
diff --git a/test/background/operators/impls/FindNextOperator.test.ts b/test/background/operators/impls/FindNextOperator.test.ts
new file mode 100644
index 0000000..0bee3f5
--- /dev/null
+++ b/test/background/operators/impls/FindNextOperator.test.ts
@@ -0,0 +1,179 @@
+import * as sinon from "sinon";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+import FindNextOperator from "../../../../src/background/operators/impls/FindNextOperator";
+import MockFindRepository from "../../mock/MockFindRepository";
+import MockFindClient from "../../mock/MockFindClient";
+import MockConsoleClient from "../../mock/MockConsoleClient";
+import MockFramePresenter from "../../mock/MockFramePresenter";
+
+describe("FindNextOperator", () => {
+ const keyword = "hello";
+ const frameIds = [0, 100, 101];
+
+ const tabPresenter = new MockTabPresenter();
+ const findRepository = new MockFindRepository();
+ const findClient = new MockFindClient();
+ const consoleClient = new MockConsoleClient();
+ const framePresenter = new MockFramePresenter();
+ const sut = new FindNextOperator(
+ tabPresenter,
+ findRepository,
+ findClient,
+ consoleClient,
+ framePresenter
+ );
+
+ let currentTabId: number;
+
+ beforeEach(async () => {
+ sinon.restore();
+
+ const currentTab = await tabPresenter.create("https://example.com/", {
+ active: true,
+ });
+ currentTabId = currentTab.id!;
+ });
+
+ describe("#run", () => {
+ it("shows errors if no previous keywords", async () => {
+ sinon
+ .stub(findRepository, "getLocalState")
+ .returns(Promise.resolve(undefined));
+
+ const mock = sinon.mock(consoleClient);
+ mock
+ .expects("showError")
+ .withArgs(currentTabId, "No previous search keywords");
+
+ await sut.run();
+
+ mock.verify();
+ });
+
+ it("continues a search on the same frame", async () => {
+ sinon.stub(findRepository, "getLocalState").returns(
+ Promise.resolve({
+ keyword,
+ frameIds,
+ framePos: 1,
+ })
+ );
+
+ const mockFindClient = sinon.mock(findClient);
+ mockFindClient
+ .expects("findNext")
+ .withArgs(currentTabId, 100, keyword)
+ .returns(Promise.resolve(true));
+
+ const mockFindRepository = sinon.mock(findRepository);
+ mockFindRepository
+ .expects("setLocalState")
+ .withArgs(currentTabId, { keyword, frameIds, framePos: 1 });
+
+ await sut.run();
+
+ mockFindRepository.verify();
+ mockFindClient.verify();
+ });
+
+ it("continues a search on next frame", async () => {
+ sinon.stub(findRepository, "getLocalState").returns(
+ Promise.resolve({
+ keyword,
+ frameIds,
+ framePos: 1,
+ })
+ );
+
+ const mockFindClient = sinon.mock(findClient);
+ mockFindClient
+ .expects("findNext")
+ .withArgs(currentTabId, 100, keyword)
+ .returns(Promise.resolve(false));
+ mockFindClient
+ .expects("clearSelection")
+ .withArgs(currentTabId, 100)
+ .returns(Promise.resolve());
+ mockFindClient
+ .expects("findNext")
+ .withArgs(currentTabId, 101, keyword)
+ .returns(Promise.resolve(true));
+
+ const mockFindRepository = sinon.mock(findRepository);
+ mockFindRepository
+ .expects("setLocalState")
+ .withArgs(currentTabId, { keyword, frameIds, framePos: 2 });
+
+ await sut.run();
+
+ mockFindRepository.verify();
+ mockFindClient.verify();
+ });
+
+ it("exercise a wrap-search", async () => {
+ sinon.stub(findRepository, "getLocalState").returns(
+ Promise.resolve({
+ keyword,
+ frameIds,
+ framePos: 2,
+ })
+ );
+
+ const mockFindClient = sinon.mock(findClient);
+ mockFindClient
+ .expects("findNext")
+ .withArgs(currentTabId, 101, keyword)
+ .returns(Promise.resolve(false));
+ mockFindClient
+ .expects("clearSelection")
+ .withArgs(currentTabId, 101)
+ .returns(Promise.resolve());
+ mockFindClient
+ .expects("findNext")
+ .withArgs(currentTabId, 0, keyword)
+ .returns(Promise.resolve(true));
+
+ const mockFindRepository = sinon.mock(findRepository);
+ mockFindRepository
+ .expects("setLocalState")
+ .withArgs(currentTabId, { keyword, frameIds, framePos: 0 });
+
+ await sut.run();
+
+ mockFindRepository.verify();
+ mockFindClient.verify();
+ });
+
+ it("starts a search with last keywords", async () => {
+ sinon
+ .stub(findRepository, "getLocalState")
+ .returns(Promise.resolve(undefined));
+ sinon
+ .stub(findRepository, "getGlobalKeyword")
+ .returns(Promise.resolve(keyword));
+ sinon
+ .stub(framePresenter, "getAllFrameIds")
+ .returns(Promise.resolve(frameIds));
+ sinon.stub(consoleClient, "showInfo").returns(Promise.resolve());
+
+ const mockFindClient = sinon.mock(findClient);
+ mockFindClient.expects("clearSelection").withArgs(currentTabId, 0);
+ mockFindClient.expects("clearSelection").withArgs(currentTabId, 100);
+ mockFindClient.expects("clearSelection").withArgs(currentTabId, 101);
+ mockFindClient
+ .expects("findNext")
+ .withArgs(currentTabId, 0, keyword)
+ .returns(Promise.resolve(true));
+
+ const mockFindRepository = sinon.mock(findRepository);
+ mockFindRepository
+ .expects("setLocalState")
+ .withArgs(currentTabId, { keyword, frameIds, framePos: 0 });
+
+ await sut.run();
+
+ mockFindRepository.verify();
+ mockFindClient.verify();
+ });
+ });
+});
diff --git a/test/background/operators/impls/FindOperatorFactoryChain.ts b/test/background/operators/impls/FindOperatorFactoryChain.ts
new file mode 100644
index 0000000..0fd234f
--- /dev/null
+++ b/test/background/operators/impls/FindOperatorFactoryChain.ts
@@ -0,0 +1,23 @@
+import "reflect-metadata";
+import { expect } from "chai";
+import TabOperatorFactoryChain from "../../../../src/background/operators/impls/TabOperatorFactoryChain";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+import * as operations from "../../../../src/shared/operations";
+import FindNextOperator from "../../../../src/background/operators/impls/FindNextOperator";
+import FindPrevOperator from "../../../../src/background/operators/impls/FindPrevOperator";
+
+describe("FindOperatorFactoryChain", () => {
+ describe("#create", () => {
+ it("returns a operator for the operation", async () => {
+ const tabPresenter = new MockTabPresenter();
+ const sut = new TabOperatorFactoryChain(tabPresenter);
+
+ expect(sut.create({ type: operations.FIND_NEXT })).to.be.instanceOf(
+ FindNextOperator
+ );
+ expect(sut.create({ type: operations.FIND_PREV })).to.be.instanceOf(
+ FindPrevOperator
+ );
+ });
+ });
+});
diff --git a/test/background/operators/impls/FindPrevOperator.test.ts b/test/background/operators/impls/FindPrevOperator.test.ts
new file mode 100644
index 0000000..ebac0dc
--- /dev/null
+++ b/test/background/operators/impls/FindPrevOperator.test.ts
@@ -0,0 +1,179 @@
+import * as sinon from "sinon";
+import MockTabPresenter from "../../mock/MockTabPresenter";
+import FindPrevOperator from "../../../../src/background/operators/impls/FindPrevOperator";
+import MockFindRepository from "../../mock/MockFindRepository";
+import MockFindClient from "../../mock/MockFindClient";
+import MockConsoleClient from "../../mock/MockConsoleClient";
+import MockFramePresenter from "../../mock/MockFramePresenter";
+
+describe("FindPrevOperator", () => {
+ const keyword = "hello";
+ const frameIds = [0, 100, 101];
+
+ const tabPresenter = new MockTabPresenter();
+ const findRepository = new MockFindRepository();
+ const findClient = new MockFindClient();
+ const consoleClient = new MockConsoleClient();
+ const framePresenter = new MockFramePresenter();
+ const sut = new FindPrevOperator(
+ tabPresenter,
+ findRepository,
+ findClient,
+ consoleClient,
+ framePresenter
+ );
+
+ let currentTabId: number;
+
+ beforeEach(async () => {
+ sinon.restore();
+
+ const currentTab = await tabPresenter.create("https://example.com/", {
+ active: true,
+ });
+ currentTabId = currentTab.id!;
+ });
+
+ describe("#run", () => {
+ it("shows errors if no previous keywords", async () => {
+ sinon
+ .stub(findRepository, "getLocalState")
+ .returns(Promise.resolve(undefined));
+
+ const mock = sinon.mock(consoleClient);
+ mock
+ .expects("showError")
+ .withArgs(currentTabId, "No previous search keywords");
+
+ await sut.run();
+
+ mock.verify();
+ });
+
+ it("continues a search on the same frame", async () => {
+ sinon.stub(findRepository, "getLocalState").returns(
+ Promise.resolve({
+ keyword,
+ frameIds,
+ framePos: 1,
+ })
+ );
+
+ const mockFindClient = sinon.mock(findClient);
+ mockFindClient
+ .expects("findPrev")
+ .withArgs(currentTabId, 100, keyword)
+ .returns(Promise.resolve(true));
+
+ const mockFindRepository = sinon.mock(findRepository);
+ mockFindRepository
+ .expects("setLocalState")
+ .withArgs(currentTabId, { keyword, frameIds, framePos: 1 });
+
+ await sut.run();
+
+ mockFindRepository.verify();
+ mockFindClient.verify();
+ });
+
+ it("continues a search on next frame", async () => {
+ sinon.stub(findRepository, "getLocalState").returns(
+ Promise.resolve({
+ keyword,
+ frameIds,
+ framePos: 1,
+ })
+ );
+
+ const mockFindClient = sinon.mock(findClient);
+ mockFindClient
+ .expects("findPrev")
+ .withArgs(currentTabId, 100, keyword)
+ .returns(Promise.resolve(false));
+ mockFindClient
+ .expects("clearSelection")
+ .withArgs(currentTabId, 100)
+ .returns(Promise.resolve());
+ mockFindClient
+ .expects("findPrev")
+ .withArgs(currentTabId, 0, keyword)
+ .returns(Promise.resolve(true));
+
+ const mockFindRepository = sinon.mock(findRepository);
+ mockFindRepository
+ .expects("setLocalState")
+ .withArgs(currentTabId, { keyword, frameIds, framePos: 0 });
+
+ await sut.run();
+
+ mockFindRepository.verify();
+ mockFindClient.verify();
+ });
+
+ it("exercise a wrap-search", async () => {
+ sinon.stub(findRepository, "getLocalState").returns(
+ Promise.resolve({
+ keyword,
+ frameIds,
+ framePos: 0,
+ })
+ );
+
+ const mockFindClient = sinon.mock(findClient);
+ mockFindClient
+ .expects("findPrev")
+ .withArgs(currentTabId, 0, keyword)
+ .returns(Promise.resolve(false));
+ mockFindClient
+ .expects("clearSelection")
+ .withArgs(currentTabId, 0)
+ .returns(Promise.resolve());
+ mockFindClient
+ .expects("findPrev")
+ .withArgs(currentTabId, 101, keyword)
+ .returns(Promise.resolve(true));
+
+ const mockFindRepository = sinon.mock(findRepository);
+ mockFindRepository
+ .expects("setLocalState")
+ .withArgs(currentTabId, { keyword, frameIds, framePos: 2 });
+
+ await sut.run();
+
+ mockFindRepository.verify();
+ mockFindClient.verify();
+ });
+
+ it("starts a search with last keywords", async () => {
+ sinon
+ .stub(findRepository, "getLocalState")
+ .returns(Promise.resolve(undefined));
+ sinon
+ .stub(findRepository, "getGlobalKeyword")
+ .returns(Promise.resolve(keyword));
+ sinon
+ .stub(framePresenter, "getAllFrameIds")
+ .returns(Promise.resolve(frameIds));
+ sinon.stub(consoleClient, "showInfo").returns(Promise.resolve());
+
+ const mockFindClient = sinon.mock(findClient);
+ mockFindClient.expects("clearSelection").withArgs(currentTabId, 0);
+ mockFindClient.expects("clearSelection").withArgs(currentTabId, 100);
+ mockFindClient.expects("clearSelection").withArgs(currentTabId, 101);
+ mockFindClient
+ .expects("findPrev")
+ .withArgs(currentTabId, 101, keyword)
+ .returns(Promise.resolve(true));
+
+ const mockFindRepository = sinon.mock(findRepository);
+ mockFindRepository
+ .expects("setLocalState")
+ .withArgs(currentTabId, { keyword, frameIds, framePos: 2 });
+
+ await sut.run();
+
+ mockFindRepository.verify();
+ mockFindClient.verify();
+ });
+ });
+});
diff --git a/test/background/operators/impls/TabOperatorFactoryChain.test.ts b/test/background/operators/impls/TabOperatorFactoryChain.test.ts
index 7ab5de0..a777973 100644
--- a/test/background/operators/impls/TabOperatorFactoryChain.test.ts
+++ b/test/background/operators/impls/TabOperatorFactoryChain.test.ts
@@ -44,12 +44,12 @@ describe("TabOperatorFactoryChain", () => {
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_LAST })).to.be.instanceOf(
+ SelectLastTabOperator
+ );
+ expect(sut.create({ type: operations.TAB_PREV_SEL })).to.be.instanceOf(
+ SelectPreviousSelectedTabOperator
+ );
expect(
sut.create({ type: operations.TAB_RELOAD, cache: false })
).to.be.instanceOf(ReloadTabOperator);
diff --git a/test/background/repositories/FindRepository.test.ts b/test/background/repositories/FindRepository.test.ts
new file mode 100644
index 0000000..a08dc6d
--- /dev/null
+++ b/test/background/repositories/FindRepository.test.ts
@@ -0,0 +1,38 @@
+import { expect } from "chai";
+import { FindRepositoryImpl } from "../../../src/background/repositories/FindRepository";
+
+describe("background/repositories/FindRepositoryImpl", () => {
+ let sut: FindRepositoryImpl;
+
+ beforeEach(() => {
+ sut = new FindRepositoryImpl();
+ });
+
+ describe("global keyword", () => {
+ it("get and set a keyword", async () => {
+ expect(await sut.getGlobalKeyword()).to.be.undefined;
+
+ await sut.setGlobalKeyword("Hello, world");
+
+ const keyword = await sut.getGlobalKeyword();
+ expect(keyword).to.equal("Hello, world");
+ });
+ });
+
+ describe("local state", () => {
+ it("get and set a keyword", async () => {
+ expect(await sut.getLocalState(10)).to.be.undefined;
+
+ await sut.setLocalState(10, {
+ keyword: "Hello, world",
+ frameIds: [20, 21],
+ framePos: 0,
+ });
+
+ const state = await sut.getLocalState(10);
+ expect(state?.keyword).to.equal("Hello, world");
+
+ expect(await sut.getLocalState(20)).to.be.undefined;
+ });
+ });
+});
diff --git a/test/background/usecases/StartFindUseCase.test.ts b/test/background/usecases/StartFindUseCase.test.ts
new file mode 100644
index 0000000..22ff9a5
--- /dev/null
+++ b/test/background/usecases/StartFindUseCase.test.ts
@@ -0,0 +1,180 @@
+import * as sinon from "sinon";
+import MockFindClient from "../mock/MockFindClient";
+import MockFindRepository from "../mock/MockFindRepository";
+import MockConsoleClient from "../mock/MockConsoleClient";
+import MockFramePresenter from "../mock/MockFramePresenter";
+import StartFindUseCase from "../../../src/background/usecases/StartFindUseCase";
+
+describe("StartFindUseCase", () => {
+ const currentTabId = 100;
+ const frameIds = [0, 100, 101];
+ const keyword = "hello";
+
+ const findClient = new MockFindClient();
+ const findRepository = new MockFindRepository();
+ const consoleClient = new MockConsoleClient();
+ const framePresenter = new MockFramePresenter();
+ const sut = new StartFindUseCase(
+ findClient,
+ findRepository,
+ consoleClient,
+ framePresenter
+ );
+
+ beforeEach(async () => {
+ sinon.restore();
+
+ sinon
+ .stub(framePresenter, "getAllFrameIds")
+ .returns(Promise.resolve(frameIds));
+ });
+
+ describe("startFind", () => {
+ it("starts a find with a keyword", async () => {
+ const mockFindClient = sinon.mock(findClient);
+ mockFindClient.expects("clearSelection").withArgs(currentTabId, 0);
+ mockFindClient.expects("clearSelection").withArgs(currentTabId, 100);
+ mockFindClient.expects("clearSelection").withArgs(currentTabId, 101);
+ mockFindClient
+ .expects("findNext")
+ .withArgs(currentTabId, 0, keyword)
+ .returns(Promise.resolve(false));
+ mockFindClient
+ .expects("findNext")
+ .withArgs(currentTabId, 100, keyword)
+ .returns(Promise.resolve(true));
+ const mockFindRepository = sinon.mock(findRepository);
+ mockFindRepository
+ .expects("setLocalState")
+ .withArgs(currentTabId, { frameIds, framePos: 1, keyword });
+ const mockConsoleClient = sinon.mock(consoleClient);
+ mockConsoleClient
+ .expects("showInfo")
+ .withArgs(currentTabId, "Pattern found: " + keyword);
+
+ await sut.startFind(currentTabId, keyword);
+
+ mockFindClient.verify();
+ mockFindRepository.verify();
+ mockConsoleClient.verify();
+ });
+
+ it("starts a find with last local state", async () => {
+ const mockFindClient = sinon.mock(findClient);
+ mockFindClient.expects("clearSelection").withArgs(currentTabId, 0);
+ mockFindClient.expects("clearSelection").withArgs(currentTabId, 100);
+ mockFindClient.expects("clearSelection").withArgs(currentTabId, 101);
+ mockFindClient
+ .expects("findNext")
+ .withArgs(currentTabId, 0, keyword)
+ .returns(Promise.resolve(false));
+ mockFindClient
+ .expects("findNext")
+ .withArgs(currentTabId, 100, keyword)
+ .returns(Promise.resolve(true));
+ const mockFindRepository = sinon.mock(findRepository);
+ mockFindRepository
+ .expects("getLocalState")
+ .withArgs(currentTabId)
+ .returns(Promise.resolve({ keyword, frameIds, framePos: 0 }));
+ mockFindRepository
+ .expects("setLocalState")
+ .withArgs(currentTabId, { frameIds, framePos: 1, keyword });
+ const mockConsoleClient = sinon.mock(consoleClient);
+ mockConsoleClient
+ .expects("showInfo")
+ .withArgs(currentTabId, "Pattern found: " + keyword);
+
+ await sut.startFind(currentTabId, undefined);
+
+ mockFindClient.verify();
+ mockFindRepository.verify();
+ mockConsoleClient.verify();
+ });
+
+ it("starts a find with last global state", async () => {
+ const mockFindClient = sinon.mock(findClient);
+ mockFindClient.expects("clearSelection").withArgs(currentTabId, 0);
+ mockFindClient.expects("clearSelection").withArgs(currentTabId, 100);
+ mockFindClient.expects("clearSelection").withArgs(currentTabId, 101);
+ mockFindClient
+ .expects("findNext")
+ .withArgs(currentTabId, 0, keyword)
+ .returns(Promise.resolve(false));
+ mockFindClient
+ .expects("findNext")
+ .withArgs(currentTabId, 100, keyword)
+ .returns(Promise.resolve(true));
+ const mockFindRepository = sinon.mock(findRepository);
+ mockFindRepository
+ .expects("getLocalState")
+ .withArgs(currentTabId)
+ .returns(Promise.resolve(undefined));
+ mockFindRepository
+ .expects("getGlobalKeyword")
+ .returns(Promise.resolve(keyword));
+ mockFindRepository
+ .expects("setLocalState")
+ .withArgs(currentTabId, { frameIds, framePos: 1, keyword });
+ const mockConsoleClient = sinon.mock(consoleClient);
+ mockConsoleClient
+ .expects("showInfo")
+ .withArgs(currentTabId, "Pattern found: " + keyword);
+
+ await sut.startFind(currentTabId, undefined);
+
+ mockFindClient.verify();
+ mockFindRepository.verify();
+ mockConsoleClient.verify();
+ });
+
+ it("shows an error when pattern not found", async () => {
+ const mockFindClient = sinon.mock(findClient);
+ mockFindClient.expects("clearSelection").withArgs(currentTabId, 0);
+ mockFindClient.expects("clearSelection").withArgs(currentTabId, 100);
+ mockFindClient.expects("clearSelection").withArgs(currentTabId, 101);
+ mockFindClient
+ .expects("findNext")
+ .withArgs(currentTabId, 0, keyword)
+ .returns(Promise.resolve(false));
+ mockFindClient
+ .expects("findNext")
+ .withArgs(currentTabId, 100, keyword)
+ .returns(Promise.resolve(false));
+ mockFindClient
+ .expects("findNext")
+ .withArgs(currentTabId, 101, keyword)
+ .returns(Promise.resolve(false));
+ const mockFindRepository = sinon.mock(findRepository);
+ mockFindRepository.expects("setLocalState").never();
+ const mockConsoleClient = sinon.mock(consoleClient);
+ mockConsoleClient
+ .expects("showError")
+ .withArgs(currentTabId, "Pattern not found: " + keyword);
+
+ await sut.startFind(currentTabId, keyword);
+
+ mockFindClient.verify();
+ mockFindRepository.verify();
+ mockConsoleClient.verify();
+ });
+
+ it("shows an error when no last keywords", async () => {
+ sinon
+ .stub(findRepository, "getLocalState")
+ .returns(Promise.resolve(undefined));
+ sinon
+ .stub(findRepository, "getGlobalKeyword")
+ .returns(Promise.resolve(undefined));
+
+ const mockConsoleClient = sinon.mock(consoleClient);
+ mockConsoleClient
+ .expects("showError")
+ .withArgs(currentTabId, "No previous search keywords");
+
+ await sut.startFind(currentTabId, undefined);
+
+ mockConsoleClient.verify();
+ });
+ });
+});
diff --git a/test/content/mock/MockFindMasterClient.ts b/test/content/mock/MockFindMasterClient.ts
deleted file mode 100644
index a035cc5..0000000
--- a/test/content/mock/MockFindMasterClient.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-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/operators/impls/FindNextOperator.test.ts b/test/content/operators/impls/FindNextOperator.test.ts
deleted file mode 100644
index d93d45e..0000000
--- a/test/content/operators/impls/FindNextOperator.test.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-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
deleted file mode 100644
index 6c599ae..0000000
--- a/test/content/operators/impls/FindOperatorFactoryChain.test.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-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
deleted file mode 100644
index 1ebde8d..0000000
--- a/test/content/operators/impls/FindPrevOperator.test.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-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/repositories/FindRepository.test.ts b/test/content/repositories/FindRepository.test.ts
deleted file mode 100644
index e0abb9d..0000000
--- a/test/content/repositories/FindRepository.test.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { FindRepositoryImpl } from "../../../src/content/repositories/FindRepository";
-import { expect } from "chai";
-
-describe("FindRepositoryImpl", () => {
- it("updates and gets last keyword", () => {
- const sut = new FindRepositoryImpl();
-
- expect(sut.getLastKeyword()).to.be.null;
-
- sut.setLastKeyword("monkey");
-
- expect(sut.getLastKeyword()).to.equal("monkey");
- });
-});
diff --git a/test/content/usecases/FindUseCase.test.ts b/test/content/usecases/FindUseCase.test.ts
deleted file mode 100644
index b53ef74..0000000
--- a/test/content/usecases/FindUseCase.test.ts
+++ /dev/null
@@ -1,160 +0,0 @@
-import FindRepository from "../../../src/content/repositories/FindRepository";
-import FindPresenter from "../../../src/content/presenters/FindPresenter";
-import FindClient from "../../../src/content/client/FindClient";
-import FindUseCase from "../../../src/content/usecases/FindUseCase";
-import MockConsoleClient from "../mock/MockConsoleClient";
-import { expect } from "chai";
-
-class MockFindRepository implements FindRepository {
- public keyword: string | null;
-
- constructor() {
- this.keyword = null;
- }
-
- getLastKeyword(): string | null {
- return this.keyword;
- }
-
- setLastKeyword(keyword: string): void {
- this.keyword = keyword;
- }
-}
-
-class MockFindPresenter implements FindPresenter {
- public document: string;
-
- public highlighted: boolean;
-
- constructor() {
- this.document = "";
- this.highlighted = false;
- }
-
- find(keyword: string, _backward: boolean): boolean {
- const found = this.document.includes(keyword);
- this.highlighted = found;
- return found;
- }
-
- clearSelection(): void {
- this.highlighted = false;
- }
-}
-
-class MockFindClient implements FindClient {
- public keyword: string | null;
-
- constructor() {
- this.keyword = null;
- }
-
- getGlobalLastKeyword(): Promise<string | null> {
- return Promise.resolve(this.keyword);
- }
-
- setGlobalLastKeyword(keyword: string): Promise<void> {
- this.keyword = keyword;
- return Promise.resolve();
- }
-}
-
-describe("FindUseCase", () => {
- let repository: MockFindRepository;
- let presenter: MockFindPresenter;
- let client: MockFindClient;
- let consoleClient: MockConsoleClient;
- let sut: FindUseCase;
-
- beforeEach(() => {
- repository = new MockFindRepository();
- presenter = new MockFindPresenter();
- client = new MockFindClient();
- consoleClient = new MockConsoleClient();
- sut = new FindUseCase(presenter, repository, client, consoleClient);
- });
-
- describe("#startFind", () => {
- it("find next by ketword", async () => {
- presenter.document = "monkey punch";
-
- await sut.startFind("monkey");
-
- expect(await presenter.highlighted).to.be.true;
- expect(await consoleClient.text).to.equal("Pattern found: monkey");
- expect(await repository.getLastKeyword()).to.equal("monkey");
- expect(await client.getGlobalLastKeyword()).to.equal("monkey");
- });
-
- it("find next by last keyword", async () => {
- presenter.document = "gorilla kick";
- repository.keyword = "gorilla";
-
- await sut.startFind(undefined);
-
- expect(await presenter.highlighted).to.be.true;
- expect(await consoleClient.text).to.equal("Pattern found: gorilla");
- expect(await repository.getLastKeyword()).to.equal("gorilla");
- expect(await client.getGlobalLastKeyword()).to.equal("gorilla");
- });
-
- it("find next by global last keyword", async () => {
- presenter.document = "chimpanzee typing";
-
- repository.keyword = null;
- client.keyword = "chimpanzee";
-
- await sut.startFind(undefined);
-
- expect(await presenter.highlighted).to.be.true;
- expect(await consoleClient.text).to.equal("Pattern found: chimpanzee");
- expect(await repository.getLastKeyword()).to.equal("chimpanzee");
- expect(await client.getGlobalLastKeyword()).to.equal("chimpanzee");
- });
-
- it("find not found error", async () => {
- presenter.document = "zoo";
-
- await sut.startFind("giraffe");
-
- expect(await presenter.highlighted).to.be.false;
- expect(await consoleClient.text).to.equal("Pattern not found: giraffe");
- expect(await repository.getLastKeyword()).to.equal("giraffe");
- expect(await client.getGlobalLastKeyword()).to.equal("giraffe");
- });
-
- it("show errors when no last keywords", async () => {
- repository.keyword = null;
- client.keyword = null;
-
- await sut.startFind(undefined);
-
- expect(await consoleClient.text).to.equal("No previous search keywords");
- expect(await consoleClient.isError).to.be.true;
- });
- });
-
- describe("#findNext", () => {
- it("finds by last keyword", async () => {
- presenter.document = "monkey punch";
- repository.keyword = "monkey";
-
- await sut.findNext();
-
- expect(await presenter.highlighted).to.be.true;
- expect(await consoleClient.text).to.equal("Pattern found: monkey");
- });
-
- it("show errors when no last keywords", async () => {
- repository.keyword = null;
- client.keyword = null;
-
- await sut.findNext();
-
- expect(await consoleClient.text).to.equal("No previous search keywords");
- expect(await consoleClient.isError).to.be.true;
- });
- });
-
- describe("#findPrev", () => {});
-});