Make find as a clean architecture
This commit is contained in:
parent
bacf83a320
commit
1ba1660269
15 changed files with 424 additions and 182 deletions
|
@ -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 };
|
|
|
@ -1,9 +1,6 @@
|
||||||
import Redux from 'redux';
|
import Redux from 'redux';
|
||||||
import * as keyUtils from '../../shared/utils/keys';
|
import * as keyUtils from '../../shared/utils/keys';
|
||||||
|
|
||||||
// Find
|
|
||||||
export const FIND_SET_KEYWORD = 'find.set.keyword';
|
|
||||||
|
|
||||||
// User input
|
// User input
|
||||||
export const INPUT_KEY_PRESS = 'input.key.press';
|
export const INPUT_KEY_PRESS = 'input.key.press';
|
||||||
export const INPUT_CLEAR_KEYS = 'input.clear.keys';
|
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 const NOOP = 'noop';
|
||||||
|
|
||||||
export interface FindSetKeywordAction extends Redux.Action {
|
|
||||||
type: typeof FIND_SET_KEYWORD;
|
|
||||||
keyword: string;
|
|
||||||
found: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface InputKeyPressAction extends Redux.Action {
|
export interface InputKeyPressAction extends Redux.Action {
|
||||||
type: typeof INPUT_KEY_PRESS;
|
type: typeof INPUT_KEY_PRESS;
|
||||||
key: keyUtils.Key;
|
key: keyUtils.Key;
|
||||||
|
@ -84,7 +75,6 @@ export interface NoopAction extends Redux.Action {
|
||||||
type: typeof NOOP;
|
type: typeof NOOP;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FindAction = FindSetKeywordAction | NoopAction;
|
|
||||||
export type InputAction = InputKeyPressAction | InputClearKeysAction;
|
export type InputAction = InputKeyPressAction | InputClearKeysAction;
|
||||||
export type FollowAction =
|
export type FollowAction =
|
||||||
FollowControllerEnableAction | FollowControllerDisableAction |
|
FollowControllerEnableAction | FollowControllerDisableAction |
|
||||||
|
@ -94,7 +84,6 @@ export type MarkAction =
|
||||||
MarkCancelAction | MarkSetLocalAction | NoopAction;
|
MarkCancelAction | MarkSetLocalAction | NoopAction;
|
||||||
|
|
||||||
export type Action =
|
export type Action =
|
||||||
FindAction |
|
|
||||||
InputAction |
|
InputAction |
|
||||||
FollowAction |
|
FollowAction |
|
||||||
MarkAction |
|
MarkAction |
|
||||||
|
|
30
src/content/client/ConsoleClient.ts
Normal file
30
src/content/client/ConsoleClient.ts
Normal file
|
@ -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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
25
src/content/client/FindClient.ts
Normal file
25
src/content/client/FindClient.ts
Normal file
|
@ -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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,12 @@
|
||||||
import * as findActions from '../../actions/find';
|
|
||||||
import * as messages from '../../../shared/messages';
|
import * as messages from '../../../shared/messages';
|
||||||
import MessageListener from '../../MessageListener';
|
import MessageListener from '../../MessageListener';
|
||||||
|
|
||||||
|
import FindUseCase from '../../usecases/FindUseCase';
|
||||||
|
|
||||||
|
let findUseCase = new FindUseCase();
|
||||||
|
|
||||||
export default class FindComponent {
|
export default class FindComponent {
|
||||||
private store: any;
|
constructor() {
|
||||||
|
|
||||||
constructor(store: any) {
|
|
||||||
this.store = store;
|
|
||||||
|
|
||||||
new MessageListener().onWebMessage(this.onMessage.bind(this));
|
new MessageListener().onWebMessage(this.onMessage.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,27 +19,18 @@ export default class FindComponent {
|
||||||
case messages.FIND_PREV:
|
case messages.FIND_PREV:
|
||||||
return this.prev();
|
return this.prev();
|
||||||
}
|
}
|
||||||
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
start(text: string) {
|
start(text: string) {
|
||||||
let state = this.store.getState().find;
|
return findUseCase.startFind(text.length === 0 ? null : text);
|
||||||
|
|
||||||
if (text.length === 0) {
|
|
||||||
return this.store.dispatch(
|
|
||||||
findActions.next(state.keyword as string, true));
|
|
||||||
}
|
|
||||||
return this.store.dispatch(findActions.next(text, true));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
next() {
|
next() {
|
||||||
let state = this.store.getState().find;
|
return findUseCase.findNext();
|
||||||
return this.store.dispatch(
|
|
||||||
findActions.next(state.keyword as string, false));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
prev() {
|
prev() {
|
||||||
let state = this.store.getState().find;
|
return findUseCase.findPrev();
|
||||||
return this.store.dispatch(
|
|
||||||
findActions.prev(state.keyword as string, false));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ export default class TopContent {
|
||||||
|
|
||||||
new CommonComponent(win, store); // eslint-disable-line no-new
|
new CommonComponent(win, store); // eslint-disable-line no-new
|
||||||
new FollowController(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
|
// TODO make component
|
||||||
consoleFrames.initialize(this.win.document);
|
consoleFrames.initialize(this.win.document);
|
||||||
|
|
59
src/content/presenters/FindPresenter.ts
Normal file
59
src/content/presenters/FindPresenter.ts
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +1,15 @@
|
||||||
import { combineReducers } from 'redux';
|
import { combineReducers } from 'redux';
|
||||||
import find, { State as FindState } from './find';
|
|
||||||
import input, { State as InputState } from './input';
|
import input, { State as InputState } from './input';
|
||||||
import followController, { State as FollowControllerState }
|
import followController, { State as FollowControllerState }
|
||||||
from './follow-controller';
|
from './follow-controller';
|
||||||
import mark, { State as MarkState } from './mark';
|
import mark, { State as MarkState } from './mark';
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
find: FindState;
|
|
||||||
input: InputState;
|
input: InputState;
|
||||||
followController: FollowControllerState;
|
followController: FollowControllerState;
|
||||||
mark: MarkState;
|
mark: MarkState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
find, input, followController, mark,
|
input, followController, mark,
|
||||||
});
|
});
|
||||||
|
|
19
src/content/repositories/FindRepository.ts
Normal file
19
src/content/repositories/FindRepository.ts
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,5 +18,4 @@ export class SettingRepositoryImpl implements SettingRepository {
|
||||||
get(): Settings {
|
get(): Settings {
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
81
src/content/usecases/FindUseCase.ts
Normal file
81
src/content/usecases/FindUseCase.ts
Normal file
|
@ -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');
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
||||||
});
|
|
||||||
});
|
|
15
test/content/repositories/FindRepository.test.ts
Normal file
15
test/content/repositories/FindRepository.test.ts
Normal file
|
@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
184
test/content/usecases/FindUseCase.test.ts
Normal file
184
test/content/usecases/FindUseCase.test.ts
Normal file
|
@ -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', () => {
|
||||||
|
});
|
||||||
|
});
|
Reference in a new issue