diff --git a/src/background/controllers/OperationController.ts b/src/background/controllers/OperationController.ts index cb5c043..1e80f54 100644 --- a/src/background/controllers/OperationController.ts +++ b/src/background/controllers/OperationController.ts @@ -84,6 +84,10 @@ export default class OperationController { return this.navigateUseCase.openLinkPrev(); case operations.NAVIGATE_LINK_NEXT: return this.navigateUseCase.openLinkNext(); + case operations.NAVIGATE_PARENT: + return this.navigateUseCase.openParent(); + case operations.NAVIGATE_ROOT: + return this.navigateUseCase.openRoot(); } throw new Error('unknown operation: ' + operation.type); } diff --git a/src/background/presenters/TabPresenter.ts b/src/background/presenters/TabPresenter.ts index 5665bf0..5e6e56e 100644 --- a/src/background/presenters/TabPresenter.ts +++ b/src/background/presenters/TabPresenter.ts @@ -36,7 +36,7 @@ export default class TabPresenter { return tabId; } - async getByKeyword(keyword: string, excludePinned = false): Promise { + async getByKeyword(keyword: string, excludePinned: boolean = false): Promise { let tabs = await browser.tabs.query({ currentWindow: true }); return tabs.filter((t) => { return t.url && t.url.toLowerCase().includes(keyword.toLowerCase()) || diff --git a/src/background/usecases/NavigateUseCase.ts b/src/background/usecases/NavigateUseCase.ts index 76e1c8e..ced2a0b 100644 --- a/src/background/usecases/NavigateUseCase.ts +++ b/src/background/usecases/NavigateUseCase.ts @@ -30,11 +30,28 @@ export default class NavigateUseCase { await this.navigateClient.linkPrev(tab.id!!); } - openParent(): Promise { - throw new Error('not implemented'); + async openParent(): Promise { + let tab = await this.tabPresenter.getCurrent(); + let url = new URL(tab.url!!); + if (url.hash !== '') { + url.hash = ''; + } else if (url.search !== '') { + url.search = ''; + } else { + const basenamePattern = /\/[^/]+$/; + const lastDirPattern = /\/[^/]+\/$/; + if (basenamePattern.test(url.pathname)) { + url.pathname = url.pathname.replace(basenamePattern, '/'); + } else if (lastDirPattern.test(url.pathname)) { + url.pathname = url.pathname.replace(lastDirPattern, '/'); + } + } + await this.tabPresenter.open(url.href); } - openRoot(): Promise { - throw new Error('not implemented'); + async openRoot(): Promise { + let tab = await this.tabPresenter.getCurrent(); + let url = new URL(tab.url!!); + await this.tabPresenter.open(url.origin); } } diff --git a/src/content/controllers/KeymapController.ts b/src/content/controllers/KeymapController.ts index 4be8f9d..4eb6955 100644 --- a/src/content/controllers/KeymapController.ts +++ b/src/content/controllers/KeymapController.ts @@ -4,7 +4,6 @@ import KeymapUseCase from '../usecases/KeymapUseCase'; import AddonEnabledUseCase from '../usecases/AddonEnabledUseCase'; import FindSlaveUseCase from '../usecases/FindSlaveUseCase'; import ScrollUseCase from '../usecases/ScrollUseCase'; -import NavigateUseCase from '../usecases/NavigateUseCase'; import FocusUseCase from '../usecases/FocusUseCase'; import ClipboardUseCase from '../usecases/ClipboardUseCase'; import BackgroundClient from '../client/BackgroundClient'; @@ -19,7 +18,6 @@ export default class KeymapController { private addonEnabledUseCase: AddonEnabledUseCase, private findSlaveUseCase: FindSlaveUseCase, private scrollUseCase: ScrollUseCase, - private navigateUseCase: NavigateUseCase, private focusUseCase: FocusUseCase, private clipbaordUseCase: ClipboardUseCase, private backgroundClient: BackgroundClient, @@ -84,12 +82,6 @@ export default class KeymapController { case operations.MARK_JUMP_PREFIX: this.markKeyUseCase.enableJumpMode(); break; - case operations.NAVIGATE_PARENT: - this.navigateUseCase.openParent(); - break; - case operations.NAVIGATE_ROOT: - this.navigateUseCase.openRoot(); - break; case operations.FOCUS_INPUT: this.focusUseCase.focusFirstInput(); break; diff --git a/src/content/presenters/NavigationPresenter.ts b/src/content/presenters/NavigationPresenter.ts index c141112..11d96ec 100644 --- a/src/content/presenters/NavigationPresenter.ts +++ b/src/content/presenters/NavigationPresenter.ts @@ -6,10 +6,6 @@ export default interface NavigationPresenter { openLinkPrev(): void; openLinkNext(): void; - - openParent(): void; - - openRoot(): void; } const REL_PATTERN: {[key: string]: RegExp} = { @@ -51,29 +47,6 @@ export class NavigationPresenterImpl implements NavigationPresenter { this.linkRel('next'); } - openParent(): void { - const loc = window.location; - if (loc.hash !== '') { - loc.hash = ''; - return; - } else if (loc.search !== '') { - loc.search = ''; - return; - } - - const basenamePattern = /\/[^/]+$/; - const lastDirPattern = /\/[^/]+\/$/; - if (basenamePattern.test(loc.pathname)) { - loc.pathname = loc.pathname.replace(basenamePattern, '/'); - } else if (lastDirPattern.test(loc.pathname)) { - loc.pathname = loc.pathname.replace(lastDirPattern, '/'); - } - } - - openRoot(): void { - window.location.href = window.location.origin; - } - // Code common to linkPrev and linkNext which navigates to the specified page. private linkRel(rel: 'prev' | 'next'): void { let link = selectLast(`link[rel~=${rel}][href]`); diff --git a/src/content/usecases/NavigateUseCase.ts b/src/content/usecases/NavigateUseCase.ts index 4711c5e..7adccfd 100644 --- a/src/content/usecases/NavigateUseCase.ts +++ b/src/content/usecases/NavigateUseCase.ts @@ -24,12 +24,4 @@ export default class NavigateUseCase { openLinkNext(): void { this.navigationPresenter.openLinkNext(); } - - openParent(): void { - this.navigationPresenter.openParent(); - } - - openRoot(): void { - this.navigationPresenter.openRoot(); - } } diff --git a/test/background/usecases/NavigateUseCase.test.ts b/test/background/usecases/NavigateUseCase.test.ts new file mode 100644 index 0000000..13f3e99 --- /dev/null +++ b/test/background/usecases/NavigateUseCase.test.ts @@ -0,0 +1,82 @@ +import TabPresenter from '../../../src/background/presenters/TabPresenter'; +import NavigateUseCase from '../../../src/background/usecases/NavigateUseCase'; +import NavigateClient from '../../../src/background/clients/NavigateClient'; +// import { expect } from 'chai'; +import * as sinon from 'sinon'; + +describe('NavigateUseCase', () => { + let sut: NavigateUseCase; + let tabPresenter: TabPresenter; + let navigateClient: NavigateClient; + beforeEach(() => { + tabPresenter = new TabPresenter(); + navigateClient = new NavigateClient(); + sut = new NavigateUseCase(tabPresenter, navigateClient); + }); + + describe('#openParent()', async () => { + it.only('opens parent directory of file', async() => { + var stub = sinon.stub(tabPresenter, 'getCurrent'); + stub.returns(Promise.resolve({ url: 'https://google.com/fruits/yellow/banana' })) + + var mock = sinon.mock(tabPresenter); + mock.expects('open').withArgs('https://google.com/fruits/yellow/'); + + await sut.openParent(); + + mock.verify(); + }); + + it.only('opens parent directory of directory', async() => { + var stub = sinon.stub(tabPresenter, 'getCurrent'); + stub.returns(Promise.resolve({ url: 'https://google.com/fruits/yellow/' })) + + var mock = sinon.mock(tabPresenter); + mock.expects('open').withArgs('https://google.com/fruits/'); + + await sut.openParent(); + + mock.verify(); + }); + + it.only('removes hash', async() => { + var stub = sinon.stub(tabPresenter, 'getCurrent'); + stub.returns(Promise.resolve({ url: 'https://google.com/#top' })) + + var mock = sinon.mock(tabPresenter); + mock.expects('open').withArgs('https://google.com/'); + + await sut.openParent(); + + mock.verify(); + }); + + it.only('removes search query', async() => { + var stub = sinon.stub(tabPresenter, 'getCurrent'); + stub.returns(Promise.resolve({ url: 'https://google.com/search?q=apple' })) + + var mock = sinon.mock(tabPresenter); + mock.expects('open').withArgs('https://google.com/search'); + + await sut.openParent(); + + mock.verify(); + }); + }); + + describe('#openRoot()', () => { + it.only('opens root direectory', async() => { + var stub = sinon.stub(tabPresenter, 'getCurrent'); + stub.returns(Promise.resolve({ + url: 'https://google.com/seach?q=apple', + })) + + var mock = sinon.mock(tabPresenter); + mock.expects('open').withArgs('https://google.com'); + + await sut.openRoot(); + + mock.verify(); + }); + }); +}); diff --git a/test/content/presenters/NavigationPresenter.test.ts b/test/content/presenters/NavigationPresenter.test.ts index c1aca9a..5b6a8ca 100644 --- a/test/content/presenters/NavigationPresenter.test.ts +++ b/test/content/presenters/NavigationPresenter.test.ts @@ -132,13 +132,4 @@ describe('NavigationPresenter', () => { 'next page' )); }); - - describe('#parent', () => { - // NOTE: not able to test location - it('removes hash', () => { - window.location.hash = '#section-1'; - sut.openParent(); - expect(document.location.hash).to.be.empty; - }); - }); });