aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/content/actions/find.ts100
-rw-r--r--src/content/actions/index.ts11
-rw-r--r--src/content/client/ConsoleClient.ts30
-rw-r--r--src/content/client/FindClient.ts25
-rw-r--r--src/content/components/top-content/find.ts26
-rw-r--r--src/content/components/top-content/index.ts2
-rw-r--r--src/content/presenters/FindPresenter.ts59
-rw-r--r--src/content/reducers/find.ts25
-rw-r--r--src/content/reducers/index.ts4
-rw-r--r--src/content/repositories/FindRepository.ts19
-rw-r--r--src/content/repositories/SettingRepository.ts1
-rw-r--r--src/content/usecases/FindUseCase.ts81
-rw-r--r--test/content/reducers/find.test.ts22
-rw-r--r--test/content/repositories/FindRepository.test.ts15
-rw-r--r--test/content/usecases/FindUseCase.test.ts184
15 files changed, 423 insertions, 181 deletions
diff --git a/src/content/actions/find.ts b/src/content/actions/find.ts
deleted file mode 100644
index 53e03ae..0000000
--- a/src/content/actions/find.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-//
-// window.find(aString, aCaseSensitive, aBackwards, aWrapAround,
-// aWholeWord, aSearchInFrames);
-//
-// NOTE: window.find is not standard API
-// https://developer.mozilla.org/en-US/docs/Web/API/Window/find
-
-import * as messages from '../../shared/messages';
-import * as actions from './index';
-import * as consoleFrames from '../console-frames';
-
-interface MyWindow extends Window {
- find(
- aString: string,
- aCaseSensitive?: boolean,
- aBackwards?: boolean,
- aWrapAround?: boolean,
- aWholeWord?: boolean,
- aSearchInFrames?: boolean,
- aShowDialog?: boolean): boolean;
-}
-
-// eslint-disable-next-line no-var, vars-on-top, init-declarations
-declare var window: MyWindow;
-
-const find = (str: string, backwards: boolean): boolean => {
- let caseSensitive = false;
- let wrapScan = true;
-
-
- // NOTE: aWholeWord dows not implemented, and aSearchInFrames does not work
- // because of same origin policy
-
- // eslint-disable-next-line no-extra-parens
- let found = window.find(str, caseSensitive, backwards, wrapScan);
- if (found) {
- return found;
- }
- let sel = window.getSelection();
- if (sel) {
- sel.removeAllRanges();
- }
-
- // eslint-disable-next-line no-extra-parens
- return window.find(str, caseSensitive, backwards, wrapScan);
-};
-
-// eslint-disable-next-line max-statements
-const findNext = async(
- currentKeyword: string, reset: boolean, backwards: boolean,
-): Promise<actions.FindAction> => {
- if (reset) {
- let sel = window.getSelection();
- if (sel) {
- sel.removeAllRanges();
- }
- }
-
- let keyword = currentKeyword;
- if (currentKeyword) {
- browser.runtime.sendMessage({
- type: messages.FIND_SET_KEYWORD,
- keyword: currentKeyword,
- });
- } else {
- keyword = await browser.runtime.sendMessage({
- type: messages.FIND_GET_KEYWORD,
- });
- }
- if (!keyword) {
- await consoleFrames.postError('No previous search keywords');
- return { type: actions.NOOP };
- }
- let found = find(keyword, backwards);
- if (found) {
- consoleFrames.postInfo('Pattern found: ' + keyword);
- } else {
- consoleFrames.postError('Pattern not found: ' + keyword);
- }
-
- return {
- type: actions.FIND_SET_KEYWORD,
- keyword,
- found,
- };
-};
-
-const next = (
- currentKeyword: string, reset: boolean,
-): Promise<actions.FindAction> => {
- return findNext(currentKeyword, reset, false);
-};
-
-const prev = (
- currentKeyword: string, reset: boolean,
-): Promise<actions.FindAction> => {
- return findNext(currentKeyword, reset, true);
-};
-
-export { next, prev };
diff --git a/src/content/actions/index.ts b/src/content/actions/index.ts
index 4e395c5..f6d19aa 100644
--- a/src/content/actions/index.ts
+++ b/src/content/actions/index.ts
@@ -1,9 +1,6 @@
import Redux from 'redux';
import * as keyUtils from '../../shared/utils/keys';
-// Find
-export const FIND_SET_KEYWORD = 'find.set.keyword';
-
// User input
export const INPUT_KEY_PRESS = 'input.key.press';
export const INPUT_CLEAR_KEYS = 'input.clear.keys';
@@ -27,12 +24,6 @@ export const MARK_SET_LOCAL = 'mark.set.local';
export const NOOP = 'noop';
-export interface FindSetKeywordAction extends Redux.Action {
- type: typeof FIND_SET_KEYWORD;
- keyword: string;
- found: boolean;
-}
-
export interface InputKeyPressAction extends Redux.Action {
type: typeof INPUT_KEY_PRESS;
key: keyUtils.Key;
@@ -84,7 +75,6 @@ export interface NoopAction extends Redux.Action {
type: typeof NOOP;
}
-export type FindAction = FindSetKeywordAction | NoopAction;
export type InputAction = InputKeyPressAction | InputClearKeysAction;
export type FollowAction =
FollowControllerEnableAction | FollowControllerDisableAction |
@@ -94,7 +84,6 @@ export type MarkAction =
MarkCancelAction | MarkSetLocalAction | NoopAction;
export type Action =
- FindAction |
InputAction |
FollowAction |
MarkAction |
diff --git a/src/content/client/ConsoleClient.ts b/src/content/client/ConsoleClient.ts
new file mode 100644
index 0000000..e7046e5
--- /dev/null
+++ b/src/content/client/ConsoleClient.ts
@@ -0,0 +1,30 @@
+import * as messages from '../../shared/messages';
+
+export default interface ConsoleClient {
+ info(text: string): Promise<void>;
+ error(text: string): Promise<void>;
+
+ // eslint-disable-next-line semi
+}
+
+export class ConsoleClientImpl implements ConsoleClient {
+ async info(text: string): Promise<void> {
+ await browser.runtime.sendMessage({
+ type: messages.CONSOLE_FRAME_MESSAGE,
+ message: {
+ type: messages.CONSOLE_SHOW_INFO,
+ text,
+ },
+ });
+ }
+
+ async error(text: string): Promise<void> {
+ await browser.runtime.sendMessage({
+ type: messages.CONSOLE_FRAME_MESSAGE,
+ message: {
+ type: messages.CONSOLE_SHOW_ERROR,
+ text,
+ },
+ });
+ }
+}
diff --git a/src/content/client/FindClient.ts b/src/content/client/FindClient.ts
new file mode 100644
index 0000000..22cd3cb
--- /dev/null
+++ b/src/content/client/FindClient.ts
@@ -0,0 +1,25 @@
+import * as messages from '../../shared/messages';
+
+export default interface FindClient {
+ getGlobalLastKeyword(): Promise<string | null>;
+
+ setGlobalLastKeyword(keyword: string): Promise<void>;
+
+ // eslint-disable-next-line semi
+}
+
+export class FindClientImpl implements FindClient {
+ async getGlobalLastKeyword(): Promise<string | null> {
+ let 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/components/top-content/find.ts b/src/content/components/top-content/find.ts
index 74b95bc..c25cbeb 100644
--- a/src/content/components/top-content/find.ts
+++ b/src/content/components/top-content/find.ts
@@ -1,13 +1,12 @@
-import * as findActions from '../../actions/find';
import * as messages from '../../../shared/messages';
import MessageListener from '../../MessageListener';
-export default class FindComponent {
- private store: any;
+import FindUseCase from '../../usecases/FindUseCase';
- constructor(store: any) {
- this.store = store;
+let findUseCase = new FindUseCase();
+export default class FindComponent {
+ constructor() {
new MessageListener().onWebMessage(this.onMessage.bind(this));
}
@@ -20,27 +19,18 @@ export default class FindComponent {
case messages.FIND_PREV:
return this.prev();
}
+ return Promise.resolve();
}
start(text: string) {
- let state = this.store.getState().find;
-
- if (text.length === 0) {
- return this.store.dispatch(
- findActions.next(state.keyword as string, true));
- }
- return this.store.dispatch(findActions.next(text, true));
+ return findUseCase.startFind(text.length === 0 ? null : text);
}
next() {
- let state = this.store.getState().find;
- return this.store.dispatch(
- findActions.next(state.keyword as string, false));
+ return findUseCase.findNext();
}
prev() {
- let state = this.store.getState().find;
- return this.store.dispatch(
- findActions.prev(state.keyword as string, false));
+ return findUseCase.findPrev();
}
}
diff --git a/src/content/components/top-content/index.ts b/src/content/components/top-content/index.ts
index 101edca..b9ef2dd 100644
--- a/src/content/components/top-content/index.ts
+++ b/src/content/components/top-content/index.ts
@@ -17,7 +17,7 @@ export default class TopContent {
new CommonComponent(win, store); // eslint-disable-line no-new
new FollowController(win, store); // eslint-disable-line no-new
- new FindComponent(store); // eslint-disable-line no-new
+ new FindComponent(); // eslint-disable-line no-new
// TODO make component
consoleFrames.initialize(this.win.document);
diff --git a/src/content/presenters/FindPresenter.ts b/src/content/presenters/FindPresenter.ts
new file mode 100644
index 0000000..6dd03f8
--- /dev/null
+++ b/src/content/presenters/FindPresenter.ts
@@ -0,0 +1,59 @@
+import ConsoleClient, { ConsoleClientImpl } from '../client/ConsoleClient';
+
+export default interface FindPresenter {
+ find(keyword: string, backwards: boolean): boolean;
+
+ clearSelection(): void;
+
+ // eslint-disable-next-line semi
+}
+
+// window.find(aString, aCaseSensitive, aBackwards, aWrapAround,
+// aWholeWord, aSearchInFrames);
+//
+// NOTE: window.find is not standard API
+// https://developer.mozilla.org/en-US/docs/Web/API/Window/find
+interface MyWindow extends Window {
+ find(
+ aString: string,
+ aCaseSensitive?: boolean,
+ aBackwards?: boolean,
+ aWrapAround?: boolean,
+ aWholeWord?: boolean,
+ aSearchInFrames?: boolean,
+ aShowDialog?: boolean): boolean;
+}
+
+// eslint-disable-next-line no-var, vars-on-top, init-declarations
+declare var window: MyWindow;
+
+export class FindPresenterImpl implements FindPresenter {
+ private consoleClient: ConsoleClient;
+
+ constructor({ consoleClient = new ConsoleClientImpl() } = {}) {
+ this.consoleClient = consoleClient;
+ }
+
+ find(keyword: string, backwards: boolean): boolean {
+ let caseSensitive = false;
+ let wrapScan = true;
+
+
+ // NOTE: aWholeWord dows not implemented, and aSearchInFrames does not work
+ // because of same origin policy
+ let found = window.find(keyword, caseSensitive, backwards, wrapScan);
+ if (found) {
+ return found;
+ }
+ this.clearSelection();
+
+ return window.find(keyword, caseSensitive, backwards, wrapScan);
+ }
+
+ clearSelection(): void {
+ let sel = window.getSelection();
+ if (sel) {
+ sel.removeAllRanges();
+ }
+ }
+}
diff --git a/src/content/reducers/find.ts b/src/content/reducers/find.ts
deleted file mode 100644
index 8c3e637..0000000
--- a/src/content/reducers/find.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import * as actions from '../actions';
-
-export interface State {
- keyword: string | null;
- found: boolean;
-}
-
-const defaultState: State = {
- keyword: null,
- found: false,
-};
-
-export default function reducer(
- state: State = defaultState,
- action: actions.FindAction,
-): State {
- switch (action.type) {
- case actions.FIND_SET_KEYWORD:
- return { ...state,
- keyword: action.keyword,
- found: action.found, };
- default:
- return state;
- }
-}
diff --git a/src/content/reducers/index.ts b/src/content/reducers/index.ts
index 21e8918..812a404 100644
--- a/src/content/reducers/index.ts
+++ b/src/content/reducers/index.ts
@@ -1,17 +1,15 @@
import { combineReducers } from 'redux';
-import find, { State as FindState } from './find';
import input, { State as InputState } from './input';
import followController, { State as FollowControllerState }
from './follow-controller';
import mark, { State as MarkState } from './mark';
export interface State {
- find: FindState;
input: InputState;
followController: FollowControllerState;
mark: MarkState;
}
export default combineReducers({
- find, input, followController, mark,
+ input, followController, mark,
});
diff --git a/src/content/repositories/FindRepository.ts b/src/content/repositories/FindRepository.ts
new file mode 100644
index 0000000..85eca40
--- /dev/null
+++ b/src/content/repositories/FindRepository.ts
@@ -0,0 +1,19 @@
+export default interface FindRepository {
+ getLastKeyword(): string | null;
+
+ setLastKeyword(keyword: string): void;
+
+ // eslint-disable-next-line semi
+}
+
+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/repositories/SettingRepository.ts b/src/content/repositories/SettingRepository.ts
index ce13c25..711b2a2 100644
--- a/src/content/repositories/SettingRepository.ts
+++ b/src/content/repositories/SettingRepository.ts
@@ -18,5 +18,4 @@ export class SettingRepositoryImpl implements SettingRepository {
get(): Settings {
return current;
}
-
}
diff --git a/src/content/usecases/FindUseCase.ts b/src/content/usecases/FindUseCase.ts
new file mode 100644
index 0000000..4fda323
--- /dev/null
+++ b/src/content/usecases/FindUseCase.ts
@@ -0,0 +1,81 @@
+import FindPresenter, { FindPresenterImpl } from '../presenters/FindPresenter';
+import FindRepository, { FindRepositoryImpl }
+ from '../repositories/FindRepository';
+import FindClient, { FindClientImpl } from '../client/FindClient';
+import ConsoleClient, { ConsoleClientImpl } from '../client/ConsoleClient';
+
+export default class FindUseCase {
+ private presenter: FindPresenter;
+
+ private repository: FindRepository;
+
+ private client: FindClient;
+
+ private consoleClient: ConsoleClient;
+
+ constructor({
+ presenter = new FindPresenterImpl() as FindPresenter,
+ repository = new FindRepositoryImpl(),
+ client = new FindClientImpl(),
+ consoleClient = new ConsoleClientImpl(),
+ } = {}) {
+ this.presenter = presenter;
+ this.repository = repository;
+ this.client = client;
+ this.consoleClient = consoleClient;
+ }
+
+ async startFind(keyword: string | null): Promise<void> {
+ this.presenter.clearSelection();
+ if (keyword) {
+ this.saveKeyword(keyword);
+ } else {
+ let lastKeyword = await this.getKeyword();
+ if (!lastKeyword) {
+ return this.showNoLastKeywordError();
+ }
+ this.saveKeyword(lastKeyword);
+ }
+ return this.findNext();
+ }
+
+ findNext(): Promise<void> {
+ return this.findNextPrev(false);
+ }
+
+ findPrev(): Promise<void> {
+ return this.findNextPrev(true);
+ }
+
+ private async findNextPrev(
+ backwards: boolean,
+ ): Promise<void> {
+ let keyword = await this.getKeyword();
+ if (!keyword) {
+ return this.showNoLastKeywordError();
+ }
+ let 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');
+ }
+}
diff --git a/test/content/reducers/find.test.ts b/test/content/reducers/find.test.ts
deleted file mode 100644
index 66a2c67..0000000
--- a/test/content/reducers/find.test.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import * as actions from 'content/actions';
-import findReducer from 'content/reducers/find';
-
-describe("find reducer", () => {
- it('return the initial state', () => {
- let state = findReducer(undefined, {});
- expect(state).to.have.property('keyword', null);
- expect(state).to.have.property('found', false);
- });
-
- it('return next state for FIND_SET_KEYWORD', () => {
- let action = {
- type: actions.FIND_SET_KEYWORD,
- keyword: 'xyz',
- found: true,
- };
- let state = findReducer({}, action);
-
- expect(state.keyword).is.equal('xyz');
- expect(state.found).to.be.true;
- });
-});
diff --git a/test/content/repositories/FindRepository.test.ts b/test/content/repositories/FindRepository.test.ts
new file mode 100644
index 0000000..dcb2dff
--- /dev/null
+++ b/test/content/repositories/FindRepository.test.ts
@@ -0,0 +1,15 @@
+import { FindRepositoryImpl } from '../../../src/content/repositories/FindRepository';
+import { expect } from 'chai';
+
+describe('FindRepositoryImpl', () => {
+ it('updates and gets last keyword', () => {
+ let 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
new file mode 100644
index 0000000..347b817
--- /dev/null
+++ b/test/content/usecases/FindUseCase.test.ts
@@ -0,0 +1,184 @@
+import FindRepository from '../../../src/content/repositories/FindRepository';
+import FindPresenter from '../../../src/content/presenters/FindPresenter';
+import ConsoleClient from '../../../src/content/client/ConsoleClient';
+import FindClient from '../../../src/content/client/FindClient';
+import FindUseCase from '../../../src/content/usecases/FindUseCase';
+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 {
+ let 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();
+ }
+}
+
+class MockConsoleClient implements ConsoleClient {
+ public isError: boolean;
+
+ public text: string;
+
+ constructor() {
+ this.isError = false;
+ this.text = '';
+ }
+
+ info(text: string): Promise<void> {
+ this.isError = false;
+ this.text = text;
+ return Promise.resolve();
+ }
+
+ error(text: string): Promise<void> {
+ this.isError = true;
+ this.text = text;
+ 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({ repository, presenter, 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(null);
+
+ 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(null);
+
+ 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(null);
+
+ 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', () => {
+ });
+});