Mark set/jump as a clean architecture
This commit is contained in:
parent
ebfb172520
commit
c6288f19d9
16 changed files with 316 additions and 137 deletions
|
@ -20,7 +20,6 @@ export const FOLLOW_CONTROLLER_BACKSPACE = 'follow.controller.backspace';
|
||||||
export const MARK_START_SET = 'mark.start.set';
|
export const MARK_START_SET = 'mark.start.set';
|
||||||
export const MARK_START_JUMP = 'mark.start.jump';
|
export const MARK_START_JUMP = 'mark.start.jump';
|
||||||
export const MARK_CANCEL = 'mark.cancel';
|
export const MARK_CANCEL = 'mark.cancel';
|
||||||
export const MARK_SET_LOCAL = 'mark.set.local';
|
|
||||||
|
|
||||||
export const NOOP = 'noop';
|
export const NOOP = 'noop';
|
||||||
|
|
||||||
|
@ -64,13 +63,6 @@ export interface MarkCancelAction extends Redux.Action {
|
||||||
type: typeof MARK_CANCEL;
|
type: typeof MARK_CANCEL;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MarkSetLocalAction extends Redux.Action {
|
|
||||||
type: typeof MARK_SET_LOCAL;
|
|
||||||
key: string;
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NoopAction extends Redux.Action {
|
export interface NoopAction extends Redux.Action {
|
||||||
type: typeof NOOP;
|
type: typeof NOOP;
|
||||||
}
|
}
|
||||||
|
@ -80,8 +72,7 @@ export type FollowAction =
|
||||||
FollowControllerEnableAction | FollowControllerDisableAction |
|
FollowControllerEnableAction | FollowControllerDisableAction |
|
||||||
FollowControllerKeyPressAction | FollowControllerBackspaceAction;
|
FollowControllerKeyPressAction | FollowControllerBackspaceAction;
|
||||||
export type MarkAction =
|
export type MarkAction =
|
||||||
MarkStartSetAction | MarkStartJumpAction |
|
MarkStartSetAction | MarkStartJumpAction | MarkCancelAction | NoopAction;
|
||||||
MarkCancelAction | MarkSetLocalAction | NoopAction;
|
|
||||||
|
|
||||||
export type Action =
|
export type Action =
|
||||||
InputAction |
|
InputAction |
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import * as actions from './index';
|
import * as actions from './index';
|
||||||
import * as messages from '../../shared/messages';
|
|
||||||
|
|
||||||
const startSet = (): actions.MarkAction => {
|
const startSet = (): actions.MarkAction => {
|
||||||
return { type: actions.MARK_START_SET };
|
return { type: actions.MARK_START_SET };
|
||||||
|
@ -13,34 +12,6 @@ const cancel = (): actions.MarkAction => {
|
||||||
return { type: actions.MARK_CANCEL };
|
return { type: actions.MARK_CANCEL };
|
||||||
};
|
};
|
||||||
|
|
||||||
const setLocal = (key: string, x: number, y: number): actions.MarkAction => {
|
|
||||||
return {
|
|
||||||
type: actions.MARK_SET_LOCAL,
|
|
||||||
key,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const setGlobal = (key: string, x: number, y: number): actions.MarkAction => {
|
|
||||||
browser.runtime.sendMessage({
|
|
||||||
type: messages.MARK_SET_GLOBAL,
|
|
||||||
key,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
});
|
|
||||||
return { type: actions.NOOP };
|
|
||||||
};
|
|
||||||
|
|
||||||
const jumpGlobal = (key: string): actions.MarkAction => {
|
|
||||||
browser.runtime.sendMessage({
|
|
||||||
type: messages.MARK_JUMP_GLOBAL,
|
|
||||||
key,
|
|
||||||
});
|
|
||||||
return { type: actions.NOOP };
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
startSet, startJump, cancel, setLocal,
|
startSet, startJump, cancel,
|
||||||
setGlobal, jumpGlobal,
|
|
||||||
};
|
};
|
||||||
|
|
28
src/content/client/MarkClient.ts
Normal file
28
src/content/client/MarkClient.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import Mark from '../domains/Mark';
|
||||||
|
import * as messages from '../../shared/messages';
|
||||||
|
|
||||||
|
export default interface MarkClient {
|
||||||
|
setGloablMark(key: string, mark: Mark): Promise<void>;
|
||||||
|
|
||||||
|
jumpGlobalMark(key: string): Promise<void>;
|
||||||
|
|
||||||
|
// eslint-disable-next-line semi
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MarkClientImpl implements MarkClient {
|
||||||
|
async setGloablMark(key: string, mark: Mark): Promise<void> {
|
||||||
|
await browser.runtime.sendMessage({
|
||||||
|
type: messages.MARK_SET_GLOBAL,
|
||||||
|
key,
|
||||||
|
x: mark.x,
|
||||||
|
y: mark.y,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async jumpGlobalMark(key: string): Promise<void> {
|
||||||
|
await browser.runtime.sendMessage({
|
||||||
|
type: messages.MARK_JUMP_GLOBAL,
|
||||||
|
key,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,22 +1,15 @@
|
||||||
import * as markActions from '../../actions/mark';
|
import * as markActions from '../../actions/mark';
|
||||||
import * as consoleFrames from '../..//console-frames';
|
import * as consoleFrames from '../..//console-frames';
|
||||||
import * as keyUtils from '../../../shared/utils/keys';
|
import * as keyUtils from '../../../shared/utils/keys';
|
||||||
import Mark from '../../Mark';
|
|
||||||
|
|
||||||
import { SettingRepositoryImpl } from '../../repositories/SettingRepository';
|
import MarkUseCase from '../../usecases/MarkUseCase';
|
||||||
import { ScrollPresenterImpl } from '../../presenters/ScrollPresenter';
|
|
||||||
|
|
||||||
let settingRepository = new SettingRepositoryImpl();
|
let markUseCase = new MarkUseCase();
|
||||||
let scrollPresenter = new ScrollPresenterImpl();
|
|
||||||
|
|
||||||
const cancelKey = (key: keyUtils.Key): boolean => {
|
const cancelKey = (key: keyUtils.Key): boolean => {
|
||||||
return key.key === 'Esc' || key.key === '[' && Boolean(key.ctrlKey);
|
return key.key === 'Esc' || key.key === '[' && Boolean(key.ctrlKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
const globalKey = (key: string): boolean => {
|
|
||||||
return (/^[A-Z0-9]$/).test(key);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class MarkComponent {
|
export default class MarkComponent {
|
||||||
private store: any;
|
private store: any;
|
||||||
|
|
||||||
|
@ -26,7 +19,6 @@ export default class MarkComponent {
|
||||||
|
|
||||||
// eslint-disable-next-line max-statements
|
// eslint-disable-next-line max-statements
|
||||||
key(key: keyUtils.Key) {
|
key(key: keyUtils.Key) {
|
||||||
let smoothscroll = settingRepository.get().properties.smoothscroll;
|
|
||||||
let { mark: markState } = this.store.getState();
|
let { mark: markState } = this.store.getState();
|
||||||
|
|
||||||
if (!markState.setMode && !markState.jumpMode) {
|
if (!markState.setMode && !markState.jumpMode) {
|
||||||
|
@ -40,45 +32,13 @@ export default class MarkComponent {
|
||||||
|
|
||||||
if (key.ctrlKey || key.metaKey || key.altKey) {
|
if (key.ctrlKey || key.metaKey || key.altKey) {
|
||||||
consoleFrames.postError('Unknown mark');
|
consoleFrames.postError('Unknown mark');
|
||||||
} else if (globalKey(key.key) && markState.setMode) {
|
|
||||||
this.doSetGlobal(key);
|
|
||||||
} else if (globalKey(key.key) && markState.jumpMode) {
|
|
||||||
this.doJumpGlobal(key);
|
|
||||||
} else if (markState.setMode) {
|
} else if (markState.setMode) {
|
||||||
this.doSet(key);
|
markUseCase.set(key.key);
|
||||||
} else if (markState.jumpMode) {
|
} else if (markState.jumpMode) {
|
||||||
this.doJump(markState.marks, key, smoothscroll);
|
markUseCase.jump(key.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.store.dispatch(markActions.cancel());
|
this.store.dispatch(markActions.cancel());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
doSet(key: keyUtils.Key) {
|
|
||||||
let { x, y } = scrollPresenter.getScroll();
|
|
||||||
this.store.dispatch(markActions.setLocal(key.key, x, y));
|
|
||||||
}
|
|
||||||
|
|
||||||
doJump(
|
|
||||||
marks: { [key: string]: Mark },
|
|
||||||
key: keyUtils.Key,
|
|
||||||
smoothscroll: boolean,
|
|
||||||
) {
|
|
||||||
if (!marks[key.key]) {
|
|
||||||
consoleFrames.postError('Mark is not set');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { x, y } = marks[key.key];
|
|
||||||
scrollPresenter.scrollTo(x, y, smoothscroll);
|
|
||||||
}
|
|
||||||
|
|
||||||
doSetGlobal(key: keyUtils.Key) {
|
|
||||||
let { x, y } = scrollPresenter.getScroll();
|
|
||||||
this.store.dispatch(markActions.setGlobal(key.key, x, y));
|
|
||||||
}
|
|
||||||
|
|
||||||
doJumpGlobal(key: keyUtils.Key) {
|
|
||||||
this.store.dispatch(markActions.jumpGlobal(key.key));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,7 +94,7 @@ class Scroller {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Point = { x: number, y: number };
|
export type Point = { x: number, y: number };
|
||||||
|
|
||||||
export default interface ScrollPresenter {
|
export default interface ScrollPresenter {
|
||||||
getScroll(): Point;
|
getScroll(): Point;
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
import Mark from '../Mark';
|
|
||||||
import * as actions from '../actions';
|
import * as actions from '../actions';
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
setMode: boolean;
|
setMode: boolean;
|
||||||
jumpMode: boolean;
|
jumpMode: boolean;
|
||||||
marks: { [key: string]: Mark };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultState: State = {
|
const defaultState: State = {
|
||||||
setMode: false,
|
setMode: false,
|
||||||
jumpMode: false,
|
jumpMode: false,
|
||||||
marks: {},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function reducer(
|
export default function reducer(
|
||||||
|
@ -24,11 +21,6 @@ export default function reducer(
|
||||||
return { ...state, jumpMode: true };
|
return { ...state, jumpMode: true };
|
||||||
case actions.MARK_CANCEL:
|
case actions.MARK_CANCEL:
|
||||||
return { ...state, setMode: false, jumpMode: false };
|
return { ...state, setMode: false, jumpMode: false };
|
||||||
case actions.MARK_SET_LOCAL: {
|
|
||||||
let marks = { ...state.marks };
|
|
||||||
marks[action.key] = { x: action.x, y: action.y };
|
|
||||||
return { ...state, setMode: false, marks };
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
25
src/content/repositories/MarkRepository.ts
Normal file
25
src/content/repositories/MarkRepository.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import Mark from '../domains/Mark';
|
||||||
|
|
||||||
|
export default interface MarkRepository {
|
||||||
|
set(key: string, mark: Mark): void;
|
||||||
|
|
||||||
|
get(key: string): Mark | null;
|
||||||
|
|
||||||
|
// eslint-disable-next-line semi
|
||||||
|
}
|
||||||
|
|
||||||
|
const saved: {[key: string]: Mark} = {};
|
||||||
|
|
||||||
|
export class MarkRepositoryImpl implements MarkRepository {
|
||||||
|
set(key: string, mark: Mark): void {
|
||||||
|
saved[key] = mark;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key: string): Mark | null {
|
||||||
|
let v = saved[key];
|
||||||
|
if (!v) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return { ...v };
|
||||||
|
}
|
||||||
|
}
|
62
src/content/usecases/MarkUseCase.ts
Normal file
62
src/content/usecases/MarkUseCase.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import ScrollPresenter, { ScrollPresenterImpl }
|
||||||
|
from '../presenters/ScrollPresenter';
|
||||||
|
import MarkClient, { MarkClientImpl } from '../client/MarkClient';
|
||||||
|
import MarkRepository, { MarkRepositoryImpl }
|
||||||
|
from '../repositories/MarkRepository';
|
||||||
|
import SettingRepository, { SettingRepositoryImpl }
|
||||||
|
from '../repositories/SettingRepository';
|
||||||
|
import ConsoleClient, { ConsoleClientImpl } from '../client/ConsoleClient';
|
||||||
|
|
||||||
|
export default class MarkUseCase {
|
||||||
|
private scrollPresenter: ScrollPresenter;
|
||||||
|
|
||||||
|
private client: MarkClient;
|
||||||
|
|
||||||
|
private repository: MarkRepository;
|
||||||
|
|
||||||
|
private settingRepository: SettingRepository;
|
||||||
|
|
||||||
|
private consoleClient: ConsoleClient;
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
scrollPresenter = new ScrollPresenterImpl(),
|
||||||
|
client = new MarkClientImpl(),
|
||||||
|
repository = new MarkRepositoryImpl(),
|
||||||
|
settingRepository = new SettingRepositoryImpl(),
|
||||||
|
consoleClient = new ConsoleClientImpl(),
|
||||||
|
} = {}) {
|
||||||
|
this.scrollPresenter = scrollPresenter;
|
||||||
|
this.client = client;
|
||||||
|
this.repository = repository;
|
||||||
|
this.settingRepository = settingRepository;
|
||||||
|
this.consoleClient = consoleClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
async set(key: string): Promise<void> {
|
||||||
|
let pos = this.scrollPresenter.getScroll();
|
||||||
|
if (this.globalKey(key)) {
|
||||||
|
this.client.setGloablMark(key, pos);
|
||||||
|
await this.consoleClient.info(`Set global mark to '${key}'`);
|
||||||
|
} else {
|
||||||
|
this.repository.set(key, pos);
|
||||||
|
await this.consoleClient.info(`Set local mark to '${key}'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async jump(key: string): Promise<void> {
|
||||||
|
if (this.globalKey(key)) {
|
||||||
|
await this.client.jumpGlobalMark(key);
|
||||||
|
} else {
|
||||||
|
let pos = this.repository.get(key);
|
||||||
|
if (!pos) {
|
||||||
|
throw new Error('Mark is not set');
|
||||||
|
}
|
||||||
|
let smooth = this.settingRepository.get().properties.smoothscroll;
|
||||||
|
this.scrollPresenter.scrollTo(pos.x, pos.y, smooth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private globalKey(key: string) {
|
||||||
|
return (/^[A-Z0-9]$/).test(key);
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,14 +22,4 @@ describe('mark actions', () => {
|
||||||
expect(action.type).to.equal(actions.MARK_CANCEL);
|
expect(action.type).to.equal(actions.MARK_CANCEL);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('setLocal', () => {
|
|
||||||
it('create setLocal action', () => {
|
|
||||||
let action = markActions.setLocal('a', 20, 30);
|
|
||||||
expect(action.type).to.equal(actions.MARK_SET_LOCAL);
|
|
||||||
expect(action.key).to.equal('a');
|
|
||||||
expect(action.x).to.equal(20);
|
|
||||||
expect(action.y).to.equal(30);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
26
test/content/mock/MockConsoleClient.ts
Normal file
26
test/content/mock/MockConsoleClient.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import ConsoleClient from '../../../src/content/client/ConsoleClient';
|
||||||
|
|
||||||
|
export default 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
47
test/content/mock/MockScrollPresenter.ts
Normal file
47
test/content/mock/MockScrollPresenter.ts
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import ScrollPresenter, { Point } from '../../../src/content/presenters/ScrollPresenter';
|
||||||
|
|
||||||
|
export default class MockScrollPresenter implements ScrollPresenter {
|
||||||
|
private pos: Point;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.pos = { x: 0, y: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
getScroll(): Point {
|
||||||
|
return this.pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollVertically(amount: number, _smooth: boolean): void {
|
||||||
|
this.pos.y += amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollHorizonally(amount: number, _smooth: boolean): void {
|
||||||
|
this.pos.x += amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollPages(amount: number, _smooth: boolean): void {
|
||||||
|
this.pos.x += amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollTo(x: number, y: number, _smooth: boolean): void {
|
||||||
|
this.pos.x = x;
|
||||||
|
this.pos.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollToTop(_smooth: boolean): void {
|
||||||
|
this.pos.y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollToBottom(_smooth: boolean): void {
|
||||||
|
this.pos.y = Infinity;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollToHome(_smooth: boolean): void {
|
||||||
|
this.pos.x = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollToEnd(_smooth: boolean): void {
|
||||||
|
this.pos.x = Infinity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ describe("mark reducer", () => {
|
||||||
let state = reducer(undefined, {});
|
let state = reducer(undefined, {});
|
||||||
expect(state.setMode).to.be.false;
|
expect(state.setMode).to.be.false;
|
||||||
expect(state.jumpMode).to.be.false;
|
expect(state.jumpMode).to.be.false;
|
||||||
expect(state.marks).to.be.empty;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('starts set mode', () => {
|
it('starts set mode', () => {
|
||||||
|
@ -29,13 +28,4 @@ describe("mark reducer", () => {
|
||||||
state = reducer({ jumpMode: true }, action);
|
state = reducer({ jumpMode: true }, action);
|
||||||
expect(state.jumpMode).to.be.false;
|
expect(state.jumpMode).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('stores local mark', () => {
|
|
||||||
let action = { type: actions.MARK_SET_LOCAL, key: 'a', x: 20, y: 30};
|
|
||||||
let state = reducer({ setMode: true }, action);
|
|
||||||
expect(state.setMode).to.be.false;
|
|
||||||
expect(state.marks['a']).to.be.an('object')
|
|
||||||
expect(state.marks['a'].x).to.equal(20)
|
|
||||||
expect(state.marks['a'].y).to.equal(30)
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
13
test/content/repositories/MarkRepository.test.ts
Normal file
13
test/content/repositories/MarkRepository.test.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { MarkRepositoryImpl } from '../../../src/content/repositories/MarkRepository';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
|
||||||
|
describe('MarkRepositoryImpl', () => {
|
||||||
|
it('save and load marks', () => {
|
||||||
|
let sut = new MarkRepositoryImpl();
|
||||||
|
|
||||||
|
sut.set('a', { x: 10, y: 20 });
|
||||||
|
expect(sut.get('a')).to.deep.equal({ x: 10, y: 20 });
|
||||||
|
expect(sut.get('b')).to.be.null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import FindRepository from '../../../src/content/repositories/FindRepository';
|
import FindRepository from '../../../src/content/repositories/FindRepository';
|
||||||
import FindPresenter from '../../../src/content/presenters/FindPresenter';
|
import FindPresenter from '../../../src/content/presenters/FindPresenter';
|
||||||
import ConsoleClient from '../../../src/content/client/ConsoleClient';
|
|
||||||
import FindClient from '../../../src/content/client/FindClient';
|
import FindClient from '../../../src/content/client/FindClient';
|
||||||
import FindUseCase from '../../../src/content/usecases/FindUseCase';
|
import FindUseCase from '../../../src/content/usecases/FindUseCase';
|
||||||
|
import MockConsoleClient from '../mock/MockConsoleClient';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
|
|
||||||
class MockFindRepository implements FindRepository {
|
class MockFindRepository implements FindRepository {
|
||||||
|
@ -59,29 +59,6 @@ class MockFindClient implements FindClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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', () => {
|
describe('FindUseCase', () => {
|
||||||
let repository: MockFindRepository;
|
let repository: MockFindRepository;
|
||||||
let presenter: MockFindPresenter;
|
let presenter: MockFindPresenter;
|
||||||
|
|
107
test/content/usecases/MarkUseCase.test.ts
Normal file
107
test/content/usecases/MarkUseCase.test.ts
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
import MarkRepository from '../../../src/content/repositories/MarkRepository';
|
||||||
|
import MarkUseCase from '../../../src/content/usecases/MarkUseCase';
|
||||||
|
import MarkClient from '../../../src/content/client/MarkClient';
|
||||||
|
import MockConsoleClient from '../mock/MockConsoleClient';
|
||||||
|
import MockScrollPresenter from '../mock/MockScrollPresenter';
|
||||||
|
import Mark from '../../../src/content/domains/Mark';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
|
||||||
|
class MockMarkRepository implements MarkRepository {
|
||||||
|
private current: {[key: string]: Mark};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.current = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key: string, mark: Mark): void {
|
||||||
|
this.current[key] = mark;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key: string): Mark | null {
|
||||||
|
return this.current[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockMarkClient implements MarkClient {
|
||||||
|
public marks: {[key: string]: Mark};
|
||||||
|
public last: string;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.marks = {};
|
||||||
|
this.last = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
setGloablMark(key: string, mark: Mark): Promise<void> {
|
||||||
|
this.marks[key] = mark;
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
jumpGlobalMark(key: string): Promise<void> {
|
||||||
|
this.last = key
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('MarkUseCase', () => {
|
||||||
|
let repository: MockMarkRepository;
|
||||||
|
let client: MockMarkClient;
|
||||||
|
let consoleClient: MockConsoleClient;
|
||||||
|
let scrollPresenter: MockScrollPresenter;
|
||||||
|
let sut: MarkUseCase;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
repository = new MockMarkRepository();
|
||||||
|
client = new MockMarkClient();
|
||||||
|
consoleClient = new MockConsoleClient();
|
||||||
|
scrollPresenter = new MockScrollPresenter();
|
||||||
|
sut = new MarkUseCase({
|
||||||
|
repository, client, consoleClient, scrollPresenter,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#set', () => {
|
||||||
|
it('sets local mark', async() => {
|
||||||
|
scrollPresenter.scrollTo(10, 20, false);
|
||||||
|
|
||||||
|
await sut.set('x');
|
||||||
|
|
||||||
|
expect(repository.get('x')).to.deep.equals({ x: 10, y: 20 });
|
||||||
|
expect(consoleClient.text).to.equal("Set local mark to 'x'");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets global mark', async() => {
|
||||||
|
scrollPresenter.scrollTo(30, 40, false);
|
||||||
|
|
||||||
|
await sut.set('Z');
|
||||||
|
|
||||||
|
expect(client.marks['Z']).to.deep.equals({ x: 30, y: 40 });
|
||||||
|
expect(consoleClient.text).to.equal("Set global mark to 'Z'");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#jump', () => {
|
||||||
|
it('jumps to local mark', async() => {
|
||||||
|
repository.set('x', { x: 20, y: 40 });
|
||||||
|
|
||||||
|
await sut.jump('x');
|
||||||
|
|
||||||
|
expect(scrollPresenter.getScroll()).to.deep.equals({ x: 20, y: 40 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws an error when no local marks', () => {
|
||||||
|
return sut.jump('a').then(() => {
|
||||||
|
throw new Error('error');
|
||||||
|
}).catch((e) => {
|
||||||
|
expect(e).to.be.instanceof(Error);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('jumps to global mark', async() => {
|
||||||
|
client.marks['Z'] = { x: 20, y: 0 };
|
||||||
|
|
||||||
|
await sut.jump('Z');
|
||||||
|
|
||||||
|
expect(client.last).to.equal('Z')
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Reference in a new issue