parent
bacf83a320
commit
1ba1660269
15 changed files with 423 additions and 181 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 }; |
@ -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, |
||||
}, |
||||
}); |
||||
} |
||||
} |
@ -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, |
||||
}); |
||||
} |
||||
} |
@ -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 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, |
||||
}); |
||||
|
@ -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; |
||||
} |
||||
} |
@ -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; |
||||
}); |
||||
}); |
@ -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'); |
||||
}); |
||||
}); |
||||
|
@ -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 new issue