From 9ff80fcac3600401c9fed053cc8422f89c404940 Mon Sep 17 00:00:00 2001 From: Shin'ya UEOKA Date: Sat, 5 Oct 2019 08:52:49 +0000 Subject: [PATCH 1/5] Add partial blacklist item --- src/content/controllers/SettingController.ts | 3 +- src/shared/settings/Blacklist.ts | 116 ++++++++++-- test/shared/settings/Blacklist.test.ts | 178 +++++++++++++------ 3 files changed, 227 insertions(+), 70 deletions(-) diff --git a/src/content/controllers/SettingController.ts b/src/content/controllers/SettingController.ts index 06273a0..e1c7f01 100644 --- a/src/content/controllers/SettingController.ts +++ b/src/content/controllers/SettingController.ts @@ -15,7 +15,8 @@ export default class SettingController { async initSettings(): Promise { try { let current = await this.settingUseCase.reload(); - let disabled = current.blacklist.includes(window.location.href); + let url = new URL(window.location.href); + let disabled = current.blacklist.includesEntireBlacklist(url); if (disabled) { this.addonEnabledUseCase.disable(); } else { diff --git a/src/shared/settings/Blacklist.ts b/src/shared/settings/Blacklist.ts index a95b606..5648611 100644 --- a/src/shared/settings/Blacklist.ts +++ b/src/shared/settings/Blacklist.ts @@ -1,39 +1,117 @@ -export type BlacklistJSON = string[]; +export type BlacklistItemJSON = string | { + url: string, + keys: string[], +}; + +export type BlacklistJSON = BlacklistItemJSON[]; -const fromWildcard = (pattern: string): RegExp => { +const regexFromWildcard = (pattern: string): RegExp => { let regexStr = '^' + pattern.replace(/\*/g, '.*') + '$'; return new RegExp(regexStr); }; +const isArrayOfString = (raw: any): boolean => { + if (!Array.isArray(raw)) { + return false; + } + for (let x of Array.from(raw)) { + if (typeof x !== 'string') { + return false; + } + } + return true; +}; + +export class BlacklistItem { + public readonly pattern: string; + + private regex: RegExp; + + public readonly partial: boolean; + + public readonly keys: string[]; + + private constructor( + pattern: string, + partial: boolean, + keys: string[] + ) { + this.pattern = pattern; + this.regex = regexFromWildcard(pattern); + this.partial = partial; + this.keys = keys; + } + + static fromJSON(raw: any): BlacklistItem { + if (typeof raw === 'string') { + return new BlacklistItem(raw, false, []); + } else if (typeof raw === 'object' && raw !== null) { + if (!('url' in raw)) { + throw new TypeError( + `missing field "url" of blacklist item: ${JSON.stringify(raw)}`); + } + if (typeof raw.url !== 'string') { + throw new TypeError( + `invalid field "url" of blacklist item: ${JSON.stringify(raw)}`); + } + if (!('keys' in raw)) { + throw new TypeError( + `missing field "keys" of blacklist item: ${JSON.stringify(raw)}`); + } + if (!isArrayOfString(raw.keys)) { + throw new TypeError( + `invalid field "keys" of blacklist item: ${JSON.stringify(raw)}`); + } + return new BlacklistItem(raw.url as string, true, raw.keys as string[]); + } + throw new TypeError( + `invalid format of blacklist item: ${JSON.stringify(raw)}`); + } + + toJSON(): BlacklistItemJSON { + if (!this.partial) { + return this.pattern; + } + return { url: this.pattern, keys: this.keys }; + } + + matches(url: URL): boolean { + return this.pattern.includes('/') + ? this.regex.test(url.host + url.pathname) + : this.regex.test(url.host); + } + + includeKey(url: URL, keys: string): boolean { + if (!this.matches(url)) { + return false; + } + return !this.partial || this.keys.includes(keys); + } +} + export default class Blacklist { constructor( - private blacklist: string[], + private blacklist: BlacklistItem[], ) { } static fromJSON(json: any): Blacklist { if (!Array.isArray(json)) { - throw new TypeError(`"blacklist" is not an array of string`); - } - for (let x of json) { - if (typeof x !== 'string') { - throw new TypeError(`"blacklist" is not an array of string`); - } + throw new TypeError('blacklist is not an array: ' + JSON.stringify(json)); } - return new Blacklist(json); + let items = Array.from(json).map(item => BlacklistItem.fromJSON(item)); + return new Blacklist(items); } toJSON(): BlacklistJSON { - return this.blacklist; + return this.blacklist.map(item => item.toJSON()); } - includes(url: string): boolean { - let u = new URL(url); - return this.blacklist.some((item) => { - if (!item.includes('/')) { - return fromWildcard(item).test(u.host); - } - return fromWildcard(item).test(u.host + u.pathname); - }); + includesEntireBlacklist(url: URL): boolean { + return this.blacklist.some(item => !item.partial && item.matches(url)); + } + + includeKey(url: URL, key: string) { + return this.blacklist.some(item => item.includeKey(url, key)); } } diff --git a/test/shared/settings/Blacklist.test.ts b/test/shared/settings/Blacklist.test.ts index fbacf5d..e7e1855 100644 --- a/test/shared/settings/Blacklist.test.ts +++ b/test/shared/settings/Blacklist.test.ts @@ -1,77 +1,155 @@ -import Blacklist from '../../../src/shared/settings/Blacklist'; +import Blacklist, { BlacklistItem } from '../../../src/shared/settings/Blacklist'; import { expect } from 'chai'; -describe('Blacklist', () => { - describe('fromJSON', () => { - it('returns empty array by empty settings', () => { - let blacklist = Blacklist.fromJSON([]); - expect(blacklist.toJSON()).to.be.empty; +describe('BlacklistItem', () => { + describe('#fromJSON', () => { + it('parses string pattern', () => { + let item = BlacklistItem.fromJSON('example.com'); + expect(item.pattern).to.equal('example.com'); + expect(item.partial).to.be.false; }); - it('returns blacklist by valid settings', () => { - let blacklist = Blacklist.fromJSON([ - 'github.com', - 'circleci.com', - ]); - - expect(blacklist.toJSON()).to.deep.equal([ - 'github.com', - 'circleci.com', - ]); + it('parses partial blacklist item', () => { + let item = BlacklistItem.fromJSON({ url: 'example.com', keys: ['j', 'k']}); + expect(item.pattern).to.equal('example.com'); + expect(item.partial).to.be.true; + expect(item.keys).to.deep.equal(['j', 'k']); }); - it('throws a TypeError by invalid settings', () => { - expect(() => Blacklist.fromJSON(null)).to.throw(TypeError); - expect(() => Blacklist.fromJSON({})).to.throw(TypeError); - expect(() => Blacklist.fromJSON([1,2,3])).to.throw(TypeError); + it('throws a TypeError', () => { + expect(() => BlacklistItem.fromJSON(null)).to.throw(TypeError); + expect(() => BlacklistItem.fromJSON(100)).to.throw(TypeError); + expect(() => BlacklistItem.fromJSON({})).to.throw(TypeError); + expect(() => BlacklistItem.fromJSON({url: 'google.com'})).to.throw(TypeError); + expect(() => BlacklistItem.fromJSON({keys: ['a']})).to.throw(TypeError); + expect(() => BlacklistItem.fromJSON({url: 'google.com', keys: 10})).to.throw(TypeError); + expect(() => BlacklistItem.fromJSON({url: 'google.com', keys: ['a', 'b', 3]})).to.throw(TypeError); }); }); - describe('#includes', () => { - it('matches by *', () => { - let blacklist = new Blacklist(['*']); - - expect(blacklist.includes('https://github.com/abc')).to.be.true; + describe('#matches', () => { + it('matches by "*"', () => { + let item = BlacklistItem.fromJSON('*'); + expect(item.matches(new URL('https://github.com/abc'))).to.be.true; }); it('matches by hostname', () => { - let blacklist = new Blacklist(['github.com']); - - expect(blacklist.includes('https://github.com')).to.be.true; - expect(blacklist.includes('https://gist.github.com')).to.be.false; - expect(blacklist.includes('https://github.com/ueokande')).to.be.true; - expect(blacklist.includes('https://github.org')).to.be.false; - expect(blacklist.includes('https://google.com/search?q=github.org')).to.be.false; + let item = BlacklistItem.fromJSON('github.com'); + expect(item.matches(new URL('https://github.com'))).to.be.true; + expect(item.matches(new URL('https://gist.github.com'))).to.be.false; + expect(item.matches(new URL('https://github.com/ueokande'))).to.be.true; + expect(item.matches(new URL('https://github.org'))).to.be.false; + expect(item.matches(new URL('https://google.com/search?q=github.org'))).to.be.false; }); it('matches by hostname with wildcard', () => { - let blacklist = new Blacklist(['*.github.com']); + let item = BlacklistItem.fromJSON('*.github.com'); - expect(blacklist.includes('https://github.com')).to.be.false; - expect(blacklist.includes('https://gist.github.com')).to.be.true; - }) + expect(item.matches(new URL('https://github.com'))).to.be.false; + expect(item.matches(new URL('https://gist.github.com'))).to.be.true; + }); it('matches by path', () => { - let blacklist = new Blacklist(['github.com/abc']); + let item = BlacklistItem.fromJSON('github.com/abc'); - expect(blacklist.includes('https://github.com/abc')).to.be.true; - expect(blacklist.includes('https://github.com/abcdef')).to.be.false; - expect(blacklist.includes('https://gist.github.com/abc')).to.be.false; - }) + expect(item.matches(new URL('https://github.com/abc'))).to.be.true; + expect(item.matches(new URL('https://github.com/abcdef'))).to.be.false; + expect(item.matches(new URL('https://gist.github.com/abc'))).to.be.false; + }); it('matches by path with wildcard', () => { - let blacklist = new Blacklist(['github.com/abc*']); + let item = BlacklistItem.fromJSON('github.com/abc*'); - expect(blacklist.includes('https://github.com/abc')).to.be.true; - expect(blacklist.includes('https://github.com/abcdef')).to.be.true; - expect(blacklist.includes('https://gist.github.com/abc')).to.be.false; - }) + expect(item.matches(new URL('https://github.com/abc'))).to.be.true; + expect(item.matches(new URL('https://github.com/abcdef'))).to.be.true; + expect(item.matches(new URL('https://gist.github.com/abc'))).to.be.false; + }); it('matches address and port', () => { - let blacklist = new Blacklist(['127.0.0.1:8888']); + let item = BlacklistItem.fromJSON('127.0.0.1:8888'); + + expect(item.matches(new URL('http://127.0.0.1:8888/'))).to.be.true; + expect(item.matches(new URL('http://127.0.0.1:8888/hello'))).to.be.true; + }); - expect(blacklist.includes('http://127.0.0.1:8888/')).to.be.true; - expect(blacklist.includes('http://127.0.0.1:8888/hello')).to.be.true; + it('matches with partial blacklist', () => { + let item = BlacklistItem.fromJSON({ url: 'google.com', keys: ['j', 'k'] }); + + expect(item.matches(new URL('https://google.com'))).to.be.true; + expect(item.matches(new URL('https://yahoo.com'))).to.be.false; }) - }) + }); + + describe('#includesPartialKeys', () => { + it('matches with partial keys', () => { + let item = BlacklistItem.fromJSON({url: 'google.com', keys: ['j', 'k']}); + + expect(item.includeKey(new URL('http://google.com/maps'), 'j')).to.be.true; + expect(item.includeKey(new URL('http://google.com/maps'), 'z')).to.be.false; + expect(item.includeKey(new URL('http://maps.google.com/'), 'j')).to.be.false; + }) + }); +}); + +describe('Blacklist', () => { + describe('#fromJSON', () => { + it('parses string list', () => { + let blacklist = Blacklist.fromJSON(['example.com', 'example.org']); + expect(blacklist.toJSON()).to.deep.equals([ + 'example.com', 'example.org', + ]); + }); + + it('parses mixed blacklist', () => { + let blacklist = Blacklist.fromJSON([ + { url: 'example.com', keys: ['j', 'k']}, + 'example.org', + ]); + expect(blacklist.toJSON()).to.deep.equals([ + { url: 'example.com', keys: ['j', 'k']}, + 'example.org', + ]); + }); + + it('parses empty blacklist', () => { + let blacklist = Blacklist.fromJSON([]); + expect(blacklist.toJSON()).to.deep.equals([]); + }); + + it('throws a TypeError', () => { + expect(() => Blacklist.fromJSON(null)).to.throw(TypeError); + expect(() => Blacklist.fromJSON(100)).to.throw(TypeError); + expect(() => Blacklist.fromJSON({})).to.throw(TypeError); + expect(() => Blacklist.fromJSON([100])).to.throw(TypeError); + expect(() => Blacklist.fromJSON([{}])).to.throw(TypeError); + }) + }); + + describe('#includesEntireBlacklist', () => { + it('matches a url with entire blacklist', () => { + let blacklist = Blacklist.fromJSON(['google.com', '*.github.com']); + expect(blacklist.includesEntireBlacklist(new URL('https://google.com'))).to.be.true; + expect(blacklist.includesEntireBlacklist(new URL('https://github.com'))).to.be.false; + expect(blacklist.includesEntireBlacklist(new URL('https://gist.github.com'))).to.be.true; + }); + + it('does not matches with partial blacklist', () => { + let blacklist = Blacklist.fromJSON(['google.com', { url: 'yahoo.com', keys: ['j', 'k'] }]); + expect(blacklist.includesEntireBlacklist(new URL('https://google.com'))).to.be.true; + expect(blacklist.includesEntireBlacklist(new URL('https://yahoo.com'))).to.be.false; + }); + }); + + describe('#includesKeys', () => { + it('matches with entire blacklist or keys in the partial blacklist', () => { + let blacklist = Blacklist.fromJSON([ + 'google.com', + { url: 'github.com', keys: ['j', 'k'] }, + ]); + + expect(blacklist.includeKey(new URL('https://google.com'), 'j')).to.be.true; + expect(blacklist.includeKey(new URL('https://github.com'), 'j')).to.be.true; + expect(blacklist.includeKey(new URL('https://github.com'), 'a')).to.be.false; + }); + }); }); From 7528fe831fa4e17e5c427e89025ac76b078a9313 Mon Sep 17 00:00:00 2001 From: Shin'ya UEOKA Date: Sun, 6 Oct 2019 12:39:56 +0000 Subject: [PATCH 2/5] Ignore keys on partial blacklist --- src/content/di.ts | 2 + src/content/repositories/AddressRepository.ts | 9 ++ src/content/usecases/KeymapUseCase.ts | 15 ++ test/content/usecases/KeymapUseCase.test.ts | 133 ++++++++++++++++++ 4 files changed, 159 insertions(+) create mode 100644 src/content/repositories/AddressRepository.ts create mode 100644 test/content/usecases/KeymapUseCase.test.ts diff --git a/src/content/di.ts b/src/content/di.ts index e18806a..63103a1 100644 --- a/src/content/di.ts +++ b/src/content/di.ts @@ -2,6 +2,7 @@ import { AddonEnabledRepositoryImpl } from './repositories/AddonEnabledRepository'; import { AddonIndicatorClientImpl } from './client/AddonIndicatorClient'; +import { AddressRepositoryImpl } from './repositories/AddressRepository'; import { ClipboardRepositoryImpl } from './repositories/ClipboardRepository'; import { ConsoleClientImpl } from './client/ConsoleClient'; import { ConsoleFramePresenterImpl } from './presenters/ConsoleFramePresenter'; @@ -31,6 +32,7 @@ import { container } from 'tsyringe'; container.register('FollowMasterClient', { useValue: new FollowMasterClientImpl(window.top) }); container.register('AddonEnabledRepository', { useClass: AddonEnabledRepositoryImpl }); container.register('AddonIndicatorClient', { useClass: AddonIndicatorClientImpl }); +container.register('AddressRepository', { useClass: AddressRepositoryImpl }); container.register('ClipboardRepository', { useClass: ClipboardRepositoryImpl }); container.register('ConsoleClient', { useClass: ConsoleClientImpl }); container.register('ConsoleFramePresenter', { useClass: ConsoleFramePresenterImpl }); diff --git a/src/content/repositories/AddressRepository.ts b/src/content/repositories/AddressRepository.ts new file mode 100644 index 0000000..6f9487b --- /dev/null +++ b/src/content/repositories/AddressRepository.ts @@ -0,0 +1,9 @@ +export default interface AddressRepository { + getCurrentURL(): URL +} + +export class AddressRepositoryImpl implements AddressRepository { + getCurrentURL(): URL { + return new URL(window.location.href); + } +} diff --git a/src/content/usecases/KeymapUseCase.ts b/src/content/usecases/KeymapUseCase.ts index 495f6d0..67d667d 100644 --- a/src/content/usecases/KeymapUseCase.ts +++ b/src/content/usecases/KeymapUseCase.ts @@ -6,6 +6,7 @@ import * as operations from '../../shared/operations'; import Keymaps from '../../shared/settings/Keymaps'; import Key from '../../shared/settings/Key'; import KeySequence from '../../shared/settings/KeySequence'; +import AddressRepository from '../repositories/AddressRepository'; type KeymapEntityMap = Map; @@ -25,11 +26,19 @@ export default class KeymapUseCase { @inject('AddonEnabledRepository') private addonEnabledRepository: AddonEnabledRepository, + + @inject('AddressRepository') + private addressRepository: AddressRepository, ) { } nextOp(key: Key): operations.Operation | null { let sequence = this.repository.enqueueKey(key); + if (sequence.length() === 1 && this.blacklistKey(key)) { + // ignore if the input starts with black list keys + this.repository.clear(); + return null; + } let keymaps = this.keymapEntityMap(); let matched = Array.from(keymaps.keys()).filter( @@ -71,4 +80,10 @@ export default class KeymapUseCase { ) as [KeySequence, operations.Operation][]; return new Map(entries); } + + private blacklistKey(key: Key): boolean { + let url = this.addressRepository.getCurrentURL(); + let blacklist = this.settingRepository.get().blacklist; + return blacklist.includeKey(url, key); + } } diff --git a/test/content/usecases/KeymapUseCase.test.ts b/test/content/usecases/KeymapUseCase.test.ts new file mode 100644 index 0000000..5f2feba --- /dev/null +++ b/test/content/usecases/KeymapUseCase.test.ts @@ -0,0 +1,133 @@ +import KeymapUseCase from '../../../src/content/usecases/KeymapUseCase'; +import {expect} from 'chai'; +import SettingRepository from "../../../src/content/repositories/SettingRepository"; +import Settings from "../../../src/shared/settings/Settings"; +import AddonEnabledRepository from "../../../src/content/repositories/AddonEnabledRepository"; +import {KeymapRepositoryImpl} from "../../../src/content/repositories/KeymapRepository"; +import Key from "../../../src/shared/settings/Key"; +import AddressRepository from "../../../src/content/repositories/AddressRepository"; + +class MockSettingRepository implements SettingRepository { + constructor( + private readonly settings: Settings, + ) { + } + + get(): Settings { + return this.settings; + } + + set(_setting: Settings): void { + throw new Error('TODO'); + } +} + +class MockAddonEnabledRepository implements AddonEnabledRepository { + constructor( + private readonly enabled: boolean, + ) { + } + + get(): boolean { + return this.enabled; + } + + set(_on: boolean): void { + throw new Error('TODO'); + } +} + +class MockAddressRepository implements AddressRepository { + constructor( + private url: URL, + ) { + } + + getCurrentURL(): URL { + return this.url; + } +} + + +describe('KeymapUseCase', () => { + it('returns matched operation', () => { + let settings = Settings.fromJSON({ + keymaps: { + k: {type: 'scroll.vertically', count: -1}, + j: {type: 'scroll.vertically', count: 1}, + gg: {type: 'scroll.top'}, + }, + }); + let sut = new KeymapUseCase( + new KeymapRepositoryImpl(), + new MockSettingRepository(settings), + new MockAddonEnabledRepository(true), + new MockAddressRepository(new URL('https://example.com')), + ); + + expect(sut.nextOp(Key.fromMapKey('k'))).to.deep.equal({type: 'scroll.vertically', count: -1}); + expect(sut.nextOp(Key.fromMapKey('j'))).to.deep.equal({type: 'scroll.vertically', count: 1}); + expect(sut.nextOp(Key.fromMapKey('g'))).to.be.null; + expect(sut.nextOp(Key.fromMapKey('g'))).to.deep.equal({type: 'scroll.top'}); + expect(sut.nextOp(Key.fromMapKey('z'))).to.be.null; + }); + + it('returns only ADDON_ENABLE and ADDON_TOGGLE_ENABLED operation', () => { + let settings = Settings.fromJSON({ + keymaps: { + k: {type: 'scroll.vertically', count: -1}, + a: {type: 'addon.enable'}, + b: {type: 'addon.toggle.enabled'}, + }, + }); + let sut = new KeymapUseCase( + new KeymapRepositoryImpl(), + new MockSettingRepository(settings), + new MockAddonEnabledRepository(false), + new MockAddressRepository(new URL('https://example.com')), + ); + + expect(sut.nextOp(Key.fromMapKey('k'))).to.be.null; + expect(sut.nextOp(Key.fromMapKey('a'))).to.deep.equal({type: 'addon.enable'}); + expect(sut.nextOp(Key.fromMapKey('b'))).to.deep.equal({type: 'addon.toggle.enabled'}); + }); + + it('blocks keys in the partial blacklist', () => { + let settings = Settings.fromJSON({ + keymaps: { + k: {type: 'scroll.vertically', count: -1}, + j: {type: 'scroll.vertically', count: 1}, + gg: {"type": "scroll.top"}, + G: {"type": "scroll.bottom"}, + }, + blacklist: [ + { url: "example.com", keys: ['g'] }, + { url: "example.org", keys: [''] } + ], + }); + + let sut = new KeymapUseCase( + new KeymapRepositoryImpl(), + new MockSettingRepository(settings), + new MockAddonEnabledRepository(true), + new MockAddressRepository(new URL('https://example.com')), + ); + + expect(sut.nextOp(Key.fromMapKey('k'))).to.deep.equal({type: 'scroll.vertically', count: -1}); + expect(sut.nextOp(Key.fromMapKey('j'))).to.deep.equal({type: 'scroll.vertically', count: 1}); + expect(sut.nextOp(Key.fromMapKey('g'))).to.be.null; + expect(sut.nextOp(Key.fromMapKey('g'))).to.be.null; + expect(sut.nextOp(Key.fromMapKey('G'))).to.deep.equal({type: 'scroll.bottom'}); + + sut = new KeymapUseCase( + new KeymapRepositoryImpl(), + new MockSettingRepository(settings), + new MockAddonEnabledRepository(true), + new MockAddressRepository(new URL('https://example.org')), + ); + + expect(sut.nextOp(Key.fromMapKey('g'))).to.be.null; + expect(sut.nextOp(Key.fromMapKey('g'))).to.deep.equal({type: 'scroll.top'}); + expect(sut.nextOp(Key.fromMapKey('G'))).to.be.null; + }); +}); From fa6dfb0395826041349c604edcbcbaa316fc95d8 Mon Sep 17 00:00:00 2001 From: Shin'ya UEOKA Date: Sun, 6 Oct 2019 12:51:43 +0000 Subject: [PATCH 3/5] Add partial blacklist form --- .../components/form/BlacklistForm.tsx | 52 ++++++------ .../components/form/PartialBlacklistForm.scss | 28 +++++++ .../components/form/PartialBlacklistForm.tsx | 79 +++++++++++++++++++ src/settings/components/index.tsx | 16 +++- src/shared/settings/Blacklist.ts | 24 ++++-- src/shared/settings/KeySequence.ts | 2 +- .../components/form/BlacklistForm.test.tsx | 25 +++--- test/shared/settings/Blacklist.test.ts | 17 ++-- 8 files changed, 185 insertions(+), 58 deletions(-) create mode 100644 src/settings/components/form/PartialBlacklistForm.scss create mode 100644 src/settings/components/form/PartialBlacklistForm.tsx diff --git a/src/settings/components/form/BlacklistForm.tsx b/src/settings/components/form/BlacklistForm.tsx index f352e41..4e96cbf 100644 --- a/src/settings/components/form/BlacklistForm.tsx +++ b/src/settings/components/form/BlacklistForm.tsx @@ -2,17 +2,17 @@ import './BlacklistForm.scss'; import AddButton from '../ui/AddButton'; import DeleteButton from '../ui/DeleteButton'; import React from 'react'; -import { BlacklistJSON } from '../../../shared/settings/Blacklist'; +import Blacklist, { BlacklistItem } from '../../../shared/settings/Blacklist'; interface Props { - value: BlacklistJSON; - onChange: (value: BlacklistJSON) => void; + value: Blacklist; + onChange: (value: Blacklist) => void; onBlur: () => void; } class BlacklistForm extends React.Component { public static defaultProps: Props = { - value: [], + value: new Blacklist([]), onChange: () => {}, onBlur: () => {}, }; @@ -20,24 +20,22 @@ class BlacklistForm extends React.Component { render() { return
{ - this.props.value - .map((item, index) => { - if (typeof item !== 'string') { - // TODO support partial blacklist; - return null; - } - return
- - -
; - }) + this.props.value.items.map((item, index) => { + if (item.partial) { + return null; + } + return
+ + +
; + }) } @@ -47,17 +45,17 @@ class BlacklistForm extends React.Component { bindValue(e: any) { let name = e.target.name; let index = e.target.getAttribute('data-index'); - let next = this.props.value.slice(); + let items = this.props.value.items; if (name === 'url') { - next[index] = e.target.value; + items[index] = new BlacklistItem(e.target.value, false, []); } else if (name === 'add') { - next.push(''); + items.push(new BlacklistItem('', false, [])); } else if (name === 'delete') { - next.splice(index, 1); + items.splice(index, 1); } - this.props.onChange(next); + this.props.onChange(new Blacklist(items)); if (name === 'delete') { this.props.onBlur(); } diff --git a/src/settings/components/form/PartialBlacklistForm.scss b/src/settings/components/form/PartialBlacklistForm.scss new file mode 100644 index 0000000..caf6f93 --- /dev/null +++ b/src/settings/components/form/PartialBlacklistForm.scss @@ -0,0 +1,28 @@ +.form-partial-blacklist-form { + @mixin row-base { + display: flex; + + .column-url { + flex: 5; + min-width: 0; + } + .column-keys { + flex: 1; + min-width: 0; + } + .column-delete { + flex: 1; + min-width: 0; + } + } + + &-header { + @include row-base; + + font-weight: bold; + } + + &-row { + @include row-base; + } +} diff --git a/src/settings/components/form/PartialBlacklistForm.tsx b/src/settings/components/form/PartialBlacklistForm.tsx new file mode 100644 index 0000000..0702913 --- /dev/null +++ b/src/settings/components/form/PartialBlacklistForm.tsx @@ -0,0 +1,79 @@ +import './PartialBlacklistForm.scss'; +import AddButton from '../ui/AddButton'; +import DeleteButton from '../ui/DeleteButton'; +import React from 'react'; +import Blacklist, { BlacklistItem } from '../../../shared/settings/Blacklist'; + +interface Props { + value: Blacklist; + onChange: (value: Blacklist) => void; + onBlur: () => void; +} + +class PartialBlacklistForm extends React.Component { + public static defaultProps: Props = { + value: new Blacklist([]), + onChange: () => {}, + onBlur: () => {}, + }; + + render() { + return
+
+
URL
+
Keys
+
+ { + this.props.value.items.map((item, index) => { + if (!item.partial) { + return null; + } + return
+ + + +
; + }) + } + +
; + } + + bindValue(e: any) { + let name = e.target.name; + let index = e.target.getAttribute('data-index'); + let items = this.props.value.items; + + if (name === 'url') { + let current = items[index]; + items[index] = new BlacklistItem(e.target.value, true, current.keys); + } else if (name === 'keys') { + let current = items[index]; + items[index] = new BlacklistItem( + current.pattern, true, e.target.value.split(',')); + } else if (name === 'add') { + items.push(new BlacklistItem('', true, [])); + } else if (name === 'delete') { + items.splice(index, 1); + } + + this.props.onChange(new Blacklist(items)); + if (name === 'delete') { + this.props.onBlur(); + } + } +} + +export default PartialBlacklistForm; diff --git a/src/settings/components/index.tsx b/src/settings/components/index.tsx index 160dd9c..3eb2dbe 100644 --- a/src/settings/components/index.tsx +++ b/src/settings/components/index.tsx @@ -6,6 +6,7 @@ import SearchForm from './form/SearchForm'; import KeymapsForm from './form/KeymapsForm'; import BlacklistForm from './form/BlacklistForm'; import PropertiesForm from './form/PropertiesForm'; +import PartialBlacklistForm from './form/PartialBlacklistForm'; import * as settingActions from '../../settings/actions/setting'; import SettingData, { FormKeymaps, FormSearch, FormSettings, JSONTextSettings, @@ -53,7 +54,15 @@ class SettingsComponent extends React.Component {
Blacklist +
+
+ Partial blacklist + @@ -138,11 +147,10 @@ class SettingsComponent extends React.Component { this.props.dispatch(settingActions.set(data)); } - bindBlacklistForm(value: any) { + bindBlacklistForm(blacklist: Blacklist) { let data = new SettingData({ source: this.props.source, - form: (this.props.form as FormSettings).buildWithBlacklist( - Blacklist.fromJSON(value)), + form: (this.props.form as FormSettings).buildWithBlacklist(blacklist), }); this.props.dispatch(settingActions.set(data)); } diff --git a/src/shared/settings/Blacklist.ts b/src/shared/settings/Blacklist.ts index 5648611..0cfbd71 100644 --- a/src/shared/settings/Blacklist.ts +++ b/src/shared/settings/Blacklist.ts @@ -1,3 +1,5 @@ +import Key from './Key'; + export type BlacklistItemJSON = string | { url: string, keys: string[], @@ -31,7 +33,9 @@ export class BlacklistItem { public readonly keys: string[]; - private constructor( + private readonly keyEntities: Key[]; + + constructor( pattern: string, partial: boolean, keys: string[] @@ -40,6 +44,7 @@ export class BlacklistItem { this.regex = regexFromWildcard(pattern); this.partial = partial; this.keys = keys; + this.keyEntities = this.keys.map(Key.fromMapKey); } static fromJSON(raw: any): BlacklistItem { @@ -81,17 +86,20 @@ export class BlacklistItem { : this.regex.test(url.host); } - includeKey(url: URL, keys: string): boolean { + includeKey(url: URL, key: Key): boolean { if (!this.matches(url)) { return false; } - return !this.partial || this.keys.includes(keys); + if (!this.partial) { + return true; + } + return this.keyEntities.some(k => k.equals(key)); } } export default class Blacklist { constructor( - private blacklist: BlacklistItem[], + public readonly items: BlacklistItem[], ) { } @@ -104,14 +112,14 @@ export default class Blacklist { } toJSON(): BlacklistJSON { - return this.blacklist.map(item => item.toJSON()); + return this.items.map(item => item.toJSON()); } includesEntireBlacklist(url: URL): boolean { - return this.blacklist.some(item => !item.partial && item.matches(url)); + return this.items.some(item => !item.partial && item.matches(url)); } - includeKey(url: URL, key: string) { - return this.blacklist.some(item => item.includeKey(url, key)); + includeKey(url: URL, key: Key) { + return this.items.some(item => item.includeKey(url, key)); } } diff --git a/src/shared/settings/KeySequence.ts b/src/shared/settings/KeySequence.ts index 4955583..abae61a 100644 --- a/src/shared/settings/KeySequence.ts +++ b/src/shared/settings/KeySequence.ts @@ -1,4 +1,4 @@ -import Key from '../../shared/settings/Key'; +import Key from './Key'; export default class KeySequence { constructor( diff --git a/test/settings/components/form/BlacklistForm.test.tsx b/test/settings/components/form/BlacklistForm.test.tsx index 2be5d96..7daf513 100644 --- a/test/settings/components/form/BlacklistForm.test.tsx +++ b/test/settings/components/form/BlacklistForm.test.tsx @@ -2,13 +2,16 @@ import React from 'react'; import ReactDOM from 'react-dom'; import ReactTestRenderer from 'react-test-renderer'; import ReactTestUtils from 'react-dom/test-utils'; -import BlacklistForm from 'settings/components/form/BlacklistForm' +import { expect } from 'chai' + +import BlacklistForm from '../../../../src/settings/components/form/BlacklistForm' +import Blacklist from '../../../../src/shared/settings/Blacklist'; describe("settings/form/BlacklistForm", () => { describe('render', () => { it('renders BlacklistForm', () => { let root = ReactTestRenderer.create( - , + , ).root; let children = root.children[0].children; @@ -43,10 +46,10 @@ describe("settings/form/BlacklistForm", () => { it('invokes onChange event on edit', (done) => { ReactTestUtils.act(() => { ReactDOM.render( { - expect(value).to.have.lengthOf(2); - expect(value).to.have.members(['gitter.im', 'www.google.com/maps*']); + let urls = value.items.map(item => item.pattern); + expect(urls).to.have.members(['gitter.im', 'www.google.com/maps*']); done(); }} />, container) @@ -60,10 +63,10 @@ describe("settings/form/BlacklistForm", () => { it('invokes onChange event on delete', (done) => { ReactTestUtils.act(() => { ReactDOM.render( { - expect(value).to.have.lengthOf(1); - expect(value).to.have.members(['www.google.com/maps*']); + let urls = value.items.map(item => item.pattern); + expect(urls).to.have.members(['www.google.com/maps*']); done(); }} />, container) @@ -76,10 +79,10 @@ describe("settings/form/BlacklistForm", () => { it('invokes onChange event on add', (done) => { ReactTestUtils.act(() => { ReactDOM.render( { - expect(value).to.have.lengthOf(2); - expect(value).to.have.members(['*.slack.com', '']); + let urls = value.items.map(item => item.pattern); + expect(urls).to.have.members(['*.slack.com', '']); done(); }} />, container); diff --git a/test/shared/settings/Blacklist.test.ts b/test/shared/settings/Blacklist.test.ts index e7e1855..133112c 100644 --- a/test/shared/settings/Blacklist.test.ts +++ b/test/shared/settings/Blacklist.test.ts @@ -1,5 +1,6 @@ import Blacklist, { BlacklistItem } from '../../../src/shared/settings/Blacklist'; import { expect } from 'chai'; +import Key from '../../../src/shared/settings/Key'; describe('BlacklistItem', () => { describe('#fromJSON', () => { @@ -82,11 +83,13 @@ describe('BlacklistItem', () => { describe('#includesPartialKeys', () => { it('matches with partial keys', () => { - let item = BlacklistItem.fromJSON({url: 'google.com', keys: ['j', 'k']}); + let item = BlacklistItem.fromJSON({url: 'google.com', keys: ['j', 'k', '']}); - expect(item.includeKey(new URL('http://google.com/maps'), 'j')).to.be.true; - expect(item.includeKey(new URL('http://google.com/maps'), 'z')).to.be.false; - expect(item.includeKey(new URL('http://maps.google.com/'), 'j')).to.be.false; + expect(item.includeKey(new URL('http://google.com/maps'), Key.fromMapKey('j'))).to.be.true; + expect(item.includeKey(new URL('http://google.com/maps'), Key.fromMapKey(''))).to.be.true; + expect(item.includeKey(new URL('http://google.com/maps'), Key.fromMapKey('z'))).to.be.false; + expect(item.includeKey(new URL('http://google.com/maps'), Key.fromMapKey('u'))).to.be.false; + expect(item.includeKey(new URL('http://maps.google.com/'), Key.fromMapKey('j'))).to.be.false; }) }); }); @@ -147,9 +150,9 @@ describe('Blacklist', () => { { url: 'github.com', keys: ['j', 'k'] }, ]); - expect(blacklist.includeKey(new URL('https://google.com'), 'j')).to.be.true; - expect(blacklist.includeKey(new URL('https://github.com'), 'j')).to.be.true; - expect(blacklist.includeKey(new URL('https://github.com'), 'a')).to.be.false; + expect(blacklist.includeKey(new URL('https://google.com'), Key.fromMapKey('j'))).to.be.true; + expect(blacklist.includeKey(new URL('https://github.com'), Key.fromMapKey('j'))).to.be.true; + expect(blacklist.includeKey(new URL('https://github.com'), Key.fromMapKey('a'))).to.be.false; }); }); }); From f59a2dd8c7ac41798e077a795ea88f3bd580e81c Mon Sep 17 00:00:00 2001 From: Shin'ya UEOKA Date: Tue, 8 Oct 2019 03:35:26 +0000 Subject: [PATCH 4/5] Add e2e test for partial blacklist --- e2e/partial_blacklist.test.ts | 61 +++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 e2e/partial_blacklist.test.ts diff --git a/e2e/partial_blacklist.test.ts b/e2e/partial_blacklist.test.ts new file mode 100644 index 0000000..e938dc6 --- /dev/null +++ b/e2e/partial_blacklist.test.ts @@ -0,0 +1,61 @@ +import * as path from 'path'; +import * as assert from 'assert'; + +import TestServer from './lib/TestServer'; +import { Builder, Lanthan } from 'lanthan'; +import { WebDriver } from 'selenium-webdriver'; +import Page from './lib/Page'; + +describe("partial blacklist test", () => { + let server = new TestServer().receiveContent('/*', + ``, + ); + let lanthan: Lanthan; + let webdriver: WebDriver; + let browser: any; + + before(async() => { + lanthan = await Builder + .forBrowser('firefox') + .spyAddon(path.join(__dirname, '..')) + .build(); + webdriver = lanthan.getWebDriver(); + browser = lanthan.getWebExtBrowser(); + await server.start(); + + let url = server.url().replace('http://', ''); + await browser.storage.local.set({ + settings: { + source: 'json', + json: `{ + "keymaps": { + "j": { "type": "scroll.vertically", "count": 1 }, + "k": { "type": "scroll.vertically", "count": -1 } + }, + "blacklist": [ + { "url": "${url}", "keys": ["k"] } + ] + }`, + }, + }); + }); + + after(async() => { + await server.stop(); + if (lanthan) { + await lanthan.quit(); + } + }); + + it('should disable keys in the partial blacklist', async () => { + let page = await Page.navigateTo(webdriver, server.url('/')); + + await page.sendKeys('j') + let scrollY = await page.getScrollY(); + assert.strictEqual(scrollY, 64); + + await page.sendKeys('k') + scrollY = await page.getScrollY(); + assert.strictEqual(scrollY, 64); + }); +}); From 68f6211aac4177f3a70a40031dabbd1b61840071 Mon Sep 17 00:00:00 2001 From: Shin'ya UEOKA Date: Tue, 8 Oct 2019 13:08:29 +0000 Subject: [PATCH 5/5] Clean e2e tests --- e2e/blacklist.test.ts | 21 ++++---- e2e/clipboard.test.ts | 16 ++++-- e2e/command_addbookmark.test.ts | 2 +- e2e/command_bdelete.test.ts | 8 +-- e2e/command_buffer.test.ts | 2 +- e2e/command_help.test.ts | 2 +- e2e/command_open.test.ts | 30 +++++++----- e2e/command_tabopen.test.ts | 20 +++++--- e2e/command_winopen.test.ts | 19 +++++--- e2e/completion.test.ts | 21 +++----- e2e/completion_buffers.test.ts | 25 ++++------ e2e/completion_open.test.ts | 55 ++++++--------------- e2e/completion_set.test.ts | 13 ++--- e2e/console.test.ts | 4 +- e2e/follow.test.ts | 18 +++---- e2e/follow_properties.test.ts | 2 +- e2e/lib/SettingRepository.ts | 18 +++++++ e2e/mark.test.ts | 2 +- e2e/navigate.test.ts | 14 +++--- e2e/options.test.ts | 14 +++--- e2e/options_form.test.ts | 16 +++--- e2e/partial_blacklist.test.ts | 27 +++++------ e2e/settings.ts | 86 --------------------------------- e2e/zoom.test.ts | 2 +- 24 files changed, 171 insertions(+), 266 deletions(-) create mode 100644 e2e/lib/SettingRepository.ts delete mode 100644 e2e/settings.ts diff --git a/e2e/blacklist.test.ts b/e2e/blacklist.test.ts index 8bf1bd8..dec9d99 100644 --- a/e2e/blacklist.test.ts +++ b/e2e/blacklist.test.ts @@ -5,10 +5,12 @@ import TestServer from './lib/TestServer'; import { Builder, Lanthan } from 'lanthan'; import { WebDriver } from 'selenium-webdriver'; import Page from './lib/Page'; +import SettingRepository from "./lib/SettingRepository"; +import Settings from "../src/shared/settings/Settings"; describe("blacklist test", () => { let server = new TestServer().receiveContent('/*', - ``, + ``, ); let lanthan: Lanthan; let webdriver: WebDriver; @@ -24,17 +26,12 @@ describe("blacklist test", () => { await server.start(); let url = server.url('/a').replace('http://', ''); - await browser.storage.local.set({ - settings: { - source: 'json', - json: `{ - "keymaps": { - "j": { "type": "scroll.vertically", "count": 1 } - }, - "blacklist": [ "${url}" ] - }`, + await new SettingRepository(browser).saveJSON(Settings.fromJSON({ + keymaps: { + j: { type: "scroll.vertically", count: 1 }, }, - }); + blacklist: [ url ], + })); }); after(async() => { @@ -46,7 +43,7 @@ describe("blacklist test", () => { it('should disable add-on if the URL is in the blacklist', async () => { let page = await Page.navigateTo(webdriver, server.url('/a')); - await page.sendKeys('j') + await page.sendKeys('j'); let scrollY = await page.getScrollY(); assert.strictEqual(scrollY, 0); diff --git a/e2e/clipboard.test.ts b/e2e/clipboard.test.ts index 2b71ade..3f2b289 100644 --- a/e2e/clipboard.test.ts +++ b/e2e/clipboard.test.ts @@ -4,10 +4,11 @@ import * as path from 'path'; import TestServer from './lib/TestServer'; import eventually from './eventually'; import * as clipboard from './lib/clipboard'; -import settings from './settings'; import { Builder, Lanthan } from 'lanthan'; import { WebDriver, Key } from 'selenium-webdriver'; import Page from './lib/Page'; +import SettingRepository from "./lib/SettingRepository"; +import Settings from "../src/shared/settings/Settings"; describe("clipboard test", () => { let server = new TestServer(12321).receiveContent('/happy', 'ok'); @@ -23,9 +24,14 @@ describe("clipboard test", () => { webdriver = lanthan.getWebDriver(); browser = lanthan.getWebExtBrowser(); - await browser.storage.local.set({ - settings, - }); + await new SettingRepository(browser).saveJSON(Settings.fromJSON({ + search: { + default: "google", + engines: { + "google": "http://127.0.0.1:12321/google?q={}", + }, + }, + })); await server.start(); }); @@ -42,7 +48,7 @@ describe("clipboard test", () => { for (let tab of tabs.slice(1)) { await browser.tabs.remove(tab.id); } - }) + }); it('should copy current URL by y', async () => { let page = await Page.navigateTo(webdriver, server.url('/#should_copy_url')); diff --git a/e2e/command_addbookmark.test.ts b/e2e/command_addbookmark.test.ts index bcc75ac..5344292 100644 --- a/e2e/command_addbookmark.test.ts +++ b/e2e/command_addbookmark.test.ts @@ -10,7 +10,7 @@ import Page from './lib/Page'; describe('addbookmark command test', () => { let server = new TestServer().receiveContent('/happy', ` - how to be happy`, + how to be happy`, ); let lanthan: Lanthan; let webdriver: WebDriver; diff --git a/e2e/command_bdelete.test.ts b/e2e/command_bdelete.test.ts index c96034d..239074e 100644 --- a/e2e/command_bdelete.test.ts +++ b/e2e/command_bdelete.test.ts @@ -36,10 +36,10 @@ describe('bdelete/bdeletes command test', () => { await browser.tabs.remove(tab.id); } await browser.tabs.update(tabs[0].id, { url: server.url('/site1'), pinned: true }); - await browser.tabs.create({ url: server.url('/site2'), pinned: true }) - await browser.tabs.create({ url: server.url('/site3'), pinned: true }) - await browser.tabs.create({ url: server.url('/site4'), }) - await browser.tabs.create({ url: server.url('/site5'), }) + await browser.tabs.create({ url: server.url('/site2'), pinned: true }); + await browser.tabs.create({ url: server.url('/site3'), pinned: true }); + await browser.tabs.create({ url: server.url('/site4'), }); + await browser.tabs.create({ url: server.url('/site5'), }); await eventually(async() => { let handles = await webdriver.getAllWindowHandles(); diff --git a/e2e/command_buffer.test.ts b/e2e/command_buffer.test.ts index 0036839..472502b 100644 --- a/e2e/command_buffer.test.ts +++ b/e2e/command_buffer.test.ts @@ -16,7 +16,7 @@ describe('buffer command test', () => { my_${req.path.slice(1)} - `); + `); }); let lanthan: Lanthan; let webdriver: WebDriver; diff --git a/e2e/command_help.test.ts b/e2e/command_help.test.ts index 9269d49..20035fd 100644 --- a/e2e/command_help.test.ts +++ b/e2e/command_help.test.ts @@ -34,7 +34,7 @@ describe("help command test", () => { beforeEach(async() => { page = await Page.navigateTo(webdriver, server.url()); - }) + }); it('should open help page by help command ', async() => { let console = await page.showConsole(); diff --git a/e2e/command_open.test.ts b/e2e/command_open.test.ts index 6fb2645..ba9c51e 100644 --- a/e2e/command_open.test.ts +++ b/e2e/command_open.test.ts @@ -2,14 +2,15 @@ import * as path from 'path'; import * as assert from 'assert'; import TestServer from './lib/TestServer'; -import settings from './settings'; import eventually from './eventually'; import { Builder, Lanthan } from 'lanthan'; import { WebDriver } from 'selenium-webdriver'; import Page from './lib/Page'; +import SettingRepository from "./lib/SettingRepository"; +import Settings from "../src/shared/settings/Settings"; describe("open command test", () => { - let server = new TestServer(12321) + let server = new TestServer() .receiveContent('/google', 'google') .receiveContent('/yahoo', 'yahoo'); let lanthan: Lanthan; @@ -25,11 +26,16 @@ describe("open command test", () => { webdriver = lanthan.getWebDriver(); browser = lanthan.getWebExtBrowser(); - await browser.storage.local.set({ - settings, - }); - await server.start(); + await new SettingRepository(browser).saveJSON(Settings.fromJSON({ + search: { + default: "google", + engines: { + "google": server.url('/google?q={}'), + "yahoo": server.url('/yahoo?q={}'), + }, + }, + })); }); after(async() => { @@ -42,7 +48,7 @@ describe("open command test", () => { beforeEach(async() => { await webdriver.switchTo().defaultContent(); page = await Page.navigateTo(webdriver, server.url()); - }) + }); it('should open default search for keywords by open command ', async() => { let console = await page.showConsole(); @@ -60,7 +66,7 @@ describe("open command test", () => { await console.execCommand('open yahoo an apple'); await eventually(async() => { - let tabs = await browser.tabs.query({ active: true }) + let tabs = await browser.tabs.query({ active: true }); let url = new URL(tabs[0].url); assert.strictEqual(url.href, server.url('/yahoo?q=an%20apple')) }); @@ -71,7 +77,7 @@ describe("open command test", () => { await console.execCommand('open'); await eventually(async() => { - let tabs = await browser.tabs.query({ active: true }) + let tabs = await browser.tabs.query({ active: true }); let url = new URL(tabs[0].url); assert.strictEqual(url.href, server.url('/google?q=')) }); @@ -82,7 +88,7 @@ describe("open command test", () => { await console.execCommand('open yahoo'); await eventually(async() => { - let tabs = await browser.tabs.query({ active: true }) + let tabs = await browser.tabs.query({ active: true }); let url = new URL(tabs[0].url); assert.strictEqual(url.href, server.url('/yahoo?q=')) }); @@ -93,7 +99,7 @@ describe("open command test", () => { await console.execCommand('open example.com'); await eventually(async() => { - let tabs = await browser.tabs.query({ active: true }) + let tabs = await browser.tabs.query({ active: true }); let url = new URL(tabs[0].url); assert.strictEqual(url.href, 'http://example.com/') }); @@ -104,7 +110,7 @@ describe("open command test", () => { await console.execCommand('open https://example.com/'); await eventually(async() => { - let tabs = await browser.tabs.query({ active: true }) + let tabs = await browser.tabs.query({ active: true }); let url = new URL(tabs[0].url); assert.strictEqual(url.href, 'https://example.com/') }); diff --git a/e2e/command_tabopen.test.ts b/e2e/command_tabopen.test.ts index 9d3da9a..b5533e6 100644 --- a/e2e/command_tabopen.test.ts +++ b/e2e/command_tabopen.test.ts @@ -2,14 +2,15 @@ import * as path from 'path'; import * as assert from 'assert'; import TestServer from './lib/TestServer'; -import settings from './settings'; import eventually from './eventually'; import { Builder, Lanthan } from 'lanthan'; import { WebDriver } from 'selenium-webdriver'; import Page from './lib/Page'; +import SettingRepository from "./lib/SettingRepository"; +import Settings from "../src/shared/settings/Settings"; describe("tabopen command test", () => { - let server = new TestServer(12321) + let server = new TestServer() .receiveContent('/google', 'google') .receiveContent('/yahoo', 'yahoo'); let lanthan: Lanthan; @@ -25,11 +26,16 @@ describe("tabopen command test", () => { webdriver = lanthan.getWebDriver(); browser = lanthan.getWebExtBrowser(); - await browser.storage.local.set({ - settings, - }); - await server.start(); + await new SettingRepository(browser).saveJSON(Settings.fromJSON({ + search: { + default: "google", + engines: { + "google": server.url('/google?q={}'), + "yahoo": server.url('/yahoo?q={}'), + }, + }, + })); }); after(async() => { @@ -46,7 +52,7 @@ describe("tabopen command test", () => { } page = await Page.navigateTo(webdriver, server.url()); - }) + }); it('should open default search for keywords by tabopen command ', async() => { let console = await page.showConsole(); diff --git a/e2e/command_winopen.test.ts b/e2e/command_winopen.test.ts index 95a0b6a..fb1348d 100644 --- a/e2e/command_winopen.test.ts +++ b/e2e/command_winopen.test.ts @@ -2,14 +2,15 @@ import * as path from 'path'; import * as assert from 'assert'; import TestServer from './lib/TestServer'; -import settings from './settings'; import eventually from './eventually'; import { Builder, Lanthan } from 'lanthan'; import { WebDriver } from 'selenium-webdriver'; import Page from './lib/Page'; +import SettingRepository from "./lib/SettingRepository"; +import Settings from "../src/shared/settings/Settings"; describe("winopen command test", () => { - let server = new TestServer(12321) + let server = new TestServer() .receiveContent('/google', 'google') .receiveContent('/yahoo', 'yahoo'); let lanthan: Lanthan; @@ -24,11 +25,17 @@ describe("winopen command test", () => { .build(); webdriver = lanthan.getWebDriver(); browser = lanthan.getWebExtBrowser(); - await browser.storage.local.set({ - settings, - }); await server.start(); + await new SettingRepository(browser).saveJSON(Settings.fromJSON({ + search: { + default: "google", + engines: { + "google": server.url('/google?q={}'), + "yahoo": server.url('/yahoo?q={}'), + }, + }, + })); }); after(async() => { @@ -45,7 +52,7 @@ describe("winopen command test", () => { } page = await Page.navigateTo(webdriver, server.url()); - }) + }); it('should open default search for keywords by winopen command ', async() => { let console = await page.showConsole(); diff --git a/e2e/completion.test.ts b/e2e/completion.test.ts index afa4432..e98e1c2 100644 --- a/e2e/completion.test.ts +++ b/e2e/completion.test.ts @@ -2,7 +2,6 @@ import * as path from 'path'; import * as assert from 'assert'; import eventually from './eventually'; -import settings from './settings'; import { Builder, Lanthan } from 'lanthan'; import { WebDriver, Key } from 'selenium-webdriver'; import Page from './lib/Page'; @@ -10,7 +9,6 @@ import Page from './lib/Page'; describe("general completion test", () => { let lanthan: Lanthan; let webdriver: WebDriver; - let browser: any; let page: Page; before(async() => { @@ -19,11 +17,6 @@ describe("general completion test", () => { .spyAddon(path.join(__dirname, '..')) .build(); webdriver = lanthan.getWebDriver(); - browser = lanthan.getWebExtBrowser(); - - await browser.storage.local.set({ - settings, - }); }); after(async() => { @@ -42,8 +35,8 @@ describe("general completion test", () => { let items = await console.getCompletions(); assert.strictEqual(items.length, 11); assert.deepStrictEqual(items[0], { type: 'title', text: 'Console Command' }); - assert.ok(items[1].text.startsWith('set')) - assert.ok(items[2].text.startsWith('open')) + assert.ok(items[1].text.startsWith('set')); + assert.ok(items[2].text.startsWith('open')); assert.ok(items[3].text.startsWith('tabopen')) }); @@ -54,8 +47,8 @@ describe("general completion test", () => { let items = await console.getCompletions(); assert.strictEqual(items.length, 4); assert.deepStrictEqual(items[0], { type: 'title', text: 'Console Command' }); - assert.ok(items[1].text.startsWith('buffer')) - assert.ok(items[2].text.startsWith('bdelete')) + assert.ok(items[1].text.startsWith('buffer')); + assert.ok(items[2].text.startsWith('bdelete')); assert.ok(items[3].text.startsWith('bdeletes')) }); @@ -74,14 +67,14 @@ describe("general completion test", () => { await console.sendKeys(Key.TAB); await eventually(async() => { let items = await console.getCompletions(); - assert.ok(items[1].highlight) + assert.ok(items[1].highlight); assert.strictEqual(await console.currentValue(), 'buffer'); }); await console.sendKeys(Key.TAB, Key.TAB); await eventually(async() => { let items = await console.getCompletions(); - assert.ok(items[3].highlight) + assert.ok(items[3].highlight); assert.strictEqual(await console.currentValue(), 'bdeletes'); }); @@ -93,7 +86,7 @@ describe("general completion test", () => { await console.sendKeys(Key.SHIFT, Key.TAB); await eventually(async() => { let items = await console.getCompletions(); - assert.ok(items[3].highlight) + assert.ok(items[3].highlight); assert.strictEqual(await console.currentValue(), 'bdeletes'); }); }); diff --git a/e2e/completion_buffers.test.ts b/e2e/completion_buffers.test.ts index b2d4201..b6e7de0 100644 --- a/e2e/completion_buffers.test.ts +++ b/e2e/completion_buffers.test.ts @@ -3,7 +3,6 @@ import * as path from 'path'; import { Request, Response } from 'express' import TestServer from './lib/TestServer'; -import settings from './settings'; import eventually from './eventually'; import { Builder, Lanthan } from 'lanthan'; import { WebDriver } from 'selenium-webdriver'; @@ -17,7 +16,7 @@ describe("completion on buffer/bdelete/bdeletes", () => { title_${req.path.slice(1)} - `); + `); }); let lanthan: Lanthan; let webdriver: WebDriver; @@ -32,10 +31,6 @@ describe("completion on buffer/bdelete/bdeletes", () => { webdriver = lanthan.getWebDriver(); browser = lanthan.getWebExtBrowser(); - await browser.storage.local.set({ - settings, - }); - await server.start(); }); @@ -53,7 +48,7 @@ describe("completion on buffer/bdelete/bdeletes", () => { } await browser.tabs.update(tabs[0].id, { url: server.url('/site1'), pinned: true }); - await browser.tabs.create({ url:server.url('/site2'), pinned: true }) + await browser.tabs.create({ url:server.url('/site2'), pinned: true }); for (let i = 3; i <= 5; ++i) { await browser.tabs.create({ url: server.url('/site' + i) }); } @@ -84,7 +79,7 @@ describe("completion on buffer/bdelete/bdeletes", () => { assert.ok(items[3].text.includes('%')); assert.ok(items[5].text.includes('#')); }); - }) + }); it('should filter items with URLs by keywords on "buffer" command', async() => { let console = await page.showConsole(); @@ -97,7 +92,7 @@ describe("completion on buffer/bdelete/bdeletes", () => { assert.ok(items[1].text.includes('title_site2')); assert.ok(items[1].text.includes(server.url('/site2'))); }); - }) + }); it('should filter items with titles by keywords on "buffer" command', async() => { let console = await page.showConsole(); @@ -108,7 +103,7 @@ describe("completion on buffer/bdelete/bdeletes", () => { assert.deepStrictEqual(items[0], { type: 'title', text: 'Buffers' }); assert.ok(items[1].text.startsWith('2:')); }); - }) + }); it('should show one item by number on "buffer" command', async() => { let console = await page.showConsole(); @@ -120,7 +115,7 @@ describe("completion on buffer/bdelete/bdeletes", () => { assert.deepStrictEqual(items[0], { type: 'title', text: 'Buffers' }); assert.ok(items[1].text.startsWith('2:')); }); - }) + }); it('should show unpinned tabs "bdelete" command', async() => { let console = await page.showConsole(); @@ -133,7 +128,7 @@ describe("completion on buffer/bdelete/bdeletes", () => { assert.ok(items[2].text.includes('site4')); assert.ok(items[3].text.includes('site5')); }); - }) + }); it('should show unpinned tabs "bdeletes" command', async() => { let console = await page.showConsole(); @@ -146,7 +141,7 @@ describe("completion on buffer/bdelete/bdeletes", () => { assert.ok(items[2].text.includes('site4')); assert.ok(items[3].text.includes('site5')); }); - }) + }); it('should show both pinned and unpinned tabs "bdelete!" command', async() => { let console = await page.showConsole(); @@ -161,7 +156,7 @@ describe("completion on buffer/bdelete/bdeletes", () => { assert.ok(items[4].text.includes('site4')); assert.ok(items[5].text.includes('site5')); }); - }) + }); it('should show both pinned and unpinned tabs "bdeletes!" command', async() => { let console = await page.showConsole(); @@ -176,5 +171,5 @@ describe("completion on buffer/bdelete/bdeletes", () => { assert.ok(items[4].text.includes('site4')); assert.ok(items[5].text.includes('site5')); }); - }) + }); }); diff --git a/e2e/completion_open.test.ts b/e2e/completion_open.test.ts index c957e2e..5f8bd11 100644 --- a/e2e/completion_open.test.ts +++ b/e2e/completion_open.test.ts @@ -1,12 +1,13 @@ import * as path from 'path'; import * as assert from 'assert'; +import Settings from "../src/shared/settings/Settings"; import TestServer from './lib/TestServer'; -import settings from './settings'; import eventually from './eventually'; import { Builder, Lanthan } from 'lanthan'; import { WebDriver } from 'selenium-webdriver'; import Page from './lib/Page'; +import SettingRepository from "./lib/SettingRepository"; describe("completion on open/tabopen/winopen commands", () => { let server = new TestServer().receiveContent('/*', 'ok'); @@ -25,10 +26,6 @@ describe("completion on open/tabopen/winopen commands", () => { webdriver = lanthan.getWebDriver(); browser = lanthan.getWebExtBrowser(); - await browser.storage.local.set({ - settings, - }); - // Add item into hitories await webdriver.navigate().to(('https://i-beam.org/404')); }); @@ -65,7 +62,7 @@ describe("completion on open/tabopen/winopen commands", () => { let items = completions.filter(x => x.type === 'item').map(x => x.text); assert.ok(items.every(x => x.includes('https://'))); }); - }) + }); it('should filter items with titles by keywords on "open" command', async() => { let console = await page.showConsole(); @@ -76,7 +73,7 @@ describe("completion on open/tabopen/winopen commands", () => { let items = completions.filter(x => x.type === 'item').map(x => x.text); assert.ok(items.every(x => x.toLowerCase().includes('getting'))); }); - }) + }); it('should filter items with titles by keywords on "tabopen" command', async() => { let console = await page.showConsole(); @@ -87,7 +84,7 @@ describe("completion on open/tabopen/winopen commands", () => { let items = completions.filter(x => x.type === 'item').map(x => x.text); assert.ok(items.every(x => x.includes('https://'))); }); - }) + }); it('should filter items with titles by keywords on "winopen" command', async() => { let console = await page.showConsole(); @@ -98,7 +95,7 @@ describe("completion on open/tabopen/winopen commands", () => { let items = completions.filter(x => x.type === 'item').map(x => x.text); assert.ok(items.every(x => x.includes('https://'))); }); - }) + }); it('should display only specified items in "complete" property by set command', async() => { let console = await page.showConsole(); @@ -127,24 +124,12 @@ describe("completion on open/tabopen/winopen commands", () => { let titles = completions.filter(x => x.type === 'title').map(x => x.text); assert.deepStrictEqual(titles, ['Bookmarks', 'Search Engines', 'Search Engines']) }); - }) + }); it('should display only specified items in "complete" property by setting', async() => { - await browser.storage.local.set({ settings: { - source: 'json', - json: `{ - "keymaps": { - ":": { "type": "command.show" } - }, - "search": { - "default": "google", - "engines": { "google": "https://google.com/search?q={}" } - }, - "properties": { - "complete": "sbh" - } - }`, - }}); + new SettingRepository(browser).saveJSON(Settings.fromJSON({ + properties: { complete: "sbh" }, + })); let console = await page.showConsole(); await console.inputKeys('open '); @@ -158,21 +143,9 @@ describe("completion on open/tabopen/winopen commands", () => { await console.close(); await (webdriver.switchTo() as any).parentFrame(); - await browser.storage.local.set({ settings: { - source: 'json', - json: `{ - "keymaps": { - ":": { "type": "command.show" } - }, - "search": { - "default": "google", - "engines": { "google": "https://google.com/search?q={}" } - }, - "properties": { - "complete": "bss" - } - }`, - }}); + new SettingRepository(browser).saveJSON(Settings.fromJSON({ + properties: { complete: "bss" }, + })); console = await page.showConsole(); await console.inputKeys('open '); @@ -182,5 +155,5 @@ describe("completion on open/tabopen/winopen commands", () => { let titles = completions.filter(x => x.type === 'title').map(x => x.text); assert.deepStrictEqual(titles, ['Bookmarks', 'Search Engines', 'Search Engines']) }); - }) + }); }); diff --git a/e2e/completion_set.test.ts b/e2e/completion_set.test.ts index 2a14b2c..facf991 100644 --- a/e2e/completion_set.test.ts +++ b/e2e/completion_set.test.ts @@ -1,7 +1,6 @@ import * as path from 'path'; import * as assert from 'assert'; -import settings from './settings'; import eventually from './eventually'; import { Builder, Lanthan } from 'lanthan'; import { WebDriver } from 'selenium-webdriver'; @@ -10,7 +9,6 @@ import Page from './lib/Page'; describe("completion on set commands", () => { let lanthan: Lanthan; let webdriver: WebDriver; - let browser: any; let page: Page; before(async() => { @@ -19,11 +17,6 @@ describe("completion on set commands", () => { .spyAddon(path.join(__dirname, '..')) .build(); webdriver = lanthan.getWebDriver(); - browser = lanthan.getWebExtBrowser(); - - await browser.storage.local.set({ - settings, - }); }); after(async() => { @@ -44,9 +37,9 @@ describe("completion on set commands", () => { let items = await console.getCompletions(); assert.strictEqual(items.length, 5); assert.deepStrictEqual(items[0], { type: 'title', text: 'Properties' }); - assert.ok(items[1].text.startsWith('hintchars')) - assert.ok(items[2].text.startsWith('smoothscroll')) - assert.ok(items[3].text.startsWith('nosmoothscroll')) + assert.ok(items[1].text.startsWith('hintchars')); + assert.ok(items[2].text.startsWith('smoothscroll')); + assert.ok(items[3].text.startsWith('nosmoothscroll')); assert.ok(items[4].text.startsWith('complete')) }); }); diff --git a/e2e/console.test.ts b/e2e/console.test.ts index 583580a..faaf695 100644 --- a/e2e/console.test.ts +++ b/e2e/console.test.ts @@ -7,8 +7,8 @@ import { WebDriver, Key } from 'selenium-webdriver'; import Page from './lib/Page'; describe("console test", () => { - let server = new TestServer().receiveContent('/', - `Hello, world!`, + let server = new TestServer().receiveContent('/', + `Hello, world!`, ); let lanthan: Lanthan; let webdriver: WebDriver; diff --git a/e2e/follow.test.ts b/e2e/follow.test.ts index fd741ef..ce3f565 100644 --- a/e2e/follow.test.ts +++ b/e2e/follow.test.ts @@ -14,13 +14,13 @@ const newApp = () => { hello - `); + `); server.receiveContent('/follow-input', ` - `); + `); server.receiveContent('/area', ` @@ -34,8 +34,8 @@ const newApp = () => { - `); - + `); + /* * test case: link2 is out of the viewport * +-----------------+ @@ -52,7 +52,7 @@ const newApp = () => {
- `); + `); /* * test case 2: link2 and link3 are out of window of the frame @@ -69,14 +69,14 @@ const newApp = () => {