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_JUMP = 'mark.start.jump'; | ||||
| export const MARK_CANCEL = 'mark.cancel'; | ||||
| export const MARK_SET_LOCAL = 'mark.set.local'; | ||||
| 
 | ||||
| export const NOOP = 'noop'; | ||||
| 
 | ||||
|  | @ -64,13 +63,6 @@ export interface MarkCancelAction extends Redux.Action { | |||
|   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 { | ||||
|   type: typeof NOOP; | ||||
| } | ||||
|  | @ -80,8 +72,7 @@ export type FollowAction = | |||
|   FollowControllerEnableAction | FollowControllerDisableAction | | ||||
|   FollowControllerKeyPressAction | FollowControllerBackspaceAction; | ||||
| export type MarkAction = | ||||
|   MarkStartSetAction | MarkStartJumpAction | | ||||
|   MarkCancelAction | MarkSetLocalAction | NoopAction; | ||||
|   MarkStartSetAction | MarkStartJumpAction | MarkCancelAction | NoopAction; | ||||
| 
 | ||||
| export type Action = | ||||
|   InputAction | | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| import * as actions from './index'; | ||||
| import * as messages from '../../shared/messages'; | ||||
| 
 | ||||
| const startSet = (): actions.MarkAction => { | ||||
|   return { type: actions.MARK_START_SET }; | ||||
|  | @ -13,34 +12,6 @@ const cancel = (): actions.MarkAction => { | |||
|   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 { | ||||
|   startSet, startJump, cancel, setLocal, | ||||
|   setGlobal, jumpGlobal, | ||||
|   startSet, startJump, cancel, | ||||
| }; | ||||
|  |  | |||
							
								
								
									
										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 consoleFrames from '../..//console-frames'; | ||||
| import * as keyUtils from '../../../shared/utils/keys'; | ||||
| import Mark from '../../Mark'; | ||||
| 
 | ||||
| import { SettingRepositoryImpl } from '../../repositories/SettingRepository'; | ||||
| import { ScrollPresenterImpl } from '../../presenters/ScrollPresenter'; | ||||
| import MarkUseCase from '../../usecases/MarkUseCase'; | ||||
| 
 | ||||
| let settingRepository = new SettingRepositoryImpl(); | ||||
| let scrollPresenter = new ScrollPresenterImpl(); | ||||
| let markUseCase = new MarkUseCase(); | ||||
| 
 | ||||
| const cancelKey = (key: keyUtils.Key): boolean => { | ||||
|   return key.key === 'Esc' || key.key === '[' && Boolean(key.ctrlKey); | ||||
| }; | ||||
| 
 | ||||
| const globalKey = (key: string): boolean => { | ||||
|   return (/^[A-Z0-9]$/).test(key); | ||||
| }; | ||||
| 
 | ||||
| export default class MarkComponent { | ||||
|   private store: any; | ||||
| 
 | ||||
|  | @ -26,7 +19,6 @@ export default class MarkComponent { | |||
| 
 | ||||
|   // eslint-disable-next-line max-statements
 | ||||
|   key(key: keyUtils.Key) { | ||||
|     let smoothscroll = settingRepository.get().properties.smoothscroll; | ||||
|     let { mark: markState } = this.store.getState(); | ||||
| 
 | ||||
|     if (!markState.setMode && !markState.jumpMode) { | ||||
|  | @ -40,45 +32,13 @@ export default class MarkComponent { | |||
| 
 | ||||
|     if (key.ctrlKey || key.metaKey || key.altKey) { | ||||
|       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) { | ||||
|       this.doSet(key); | ||||
|       markUseCase.set(key.key); | ||||
|     } else if (markState.jumpMode) { | ||||
|       this.doJump(markState.marks, key, smoothscroll); | ||||
|       markUseCase.jump(key.key); | ||||
|     } | ||||
| 
 | ||||
|     this.store.dispatch(markActions.cancel()); | ||||
|     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 { | ||||
|   getScroll(): Point; | ||||
|  |  | |||
|  | @ -1,16 +1,13 @@ | |||
| import Mark from '../Mark'; | ||||
| import * as actions from '../actions'; | ||||
| 
 | ||||
| export interface State { | ||||
|   setMode: boolean; | ||||
|   jumpMode: boolean; | ||||
|   marks: { [key: string]: Mark }; | ||||
| } | ||||
| 
 | ||||
| const defaultState: State = { | ||||
|   setMode: false, | ||||
|   jumpMode: false, | ||||
|   marks: {}, | ||||
| }; | ||||
| 
 | ||||
| export default function reducer( | ||||
|  | @ -24,11 +21,6 @@ export default function reducer( | |||
|     return { ...state, jumpMode: true }; | ||||
|   case actions.MARK_CANCEL: | ||||
|     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: | ||||
|     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); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   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, {}); | ||||
|     expect(state.setMode).to.be.false; | ||||
|     expect(state.jumpMode).to.be.false; | ||||
|     expect(state.marks).to.be.empty; | ||||
|   }); | ||||
| 
 | ||||
|   it('starts set mode', () => { | ||||
|  | @ -29,13 +28,4 @@ describe("mark reducer", () => { | |||
|     state = reducer({ jumpMode: true }, action); | ||||
|     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 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 MockConsoleClient from '../mock/MockConsoleClient'; | ||||
| import { expect } from 'chai'; | ||||
| 
 | ||||
| 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', () => { | ||||
|   let repository: MockFindRepository; | ||||
|   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