Ignore keys on partial blacklist

jh-changes
Shin'ya UEOKA 5 years ago
parent 9ff80fcac3
commit 7528fe831f
  1. 2
      src/content/di.ts
  2. 9
      src/content/repositories/AddressRepository.ts
  3. 15
      src/content/usecases/KeymapUseCase.ts
  4. 133
      test/content/usecases/KeymapUseCase.test.ts

@ -2,6 +2,7 @@
import { AddonEnabledRepositoryImpl } from './repositories/AddonEnabledRepository'; import { AddonEnabledRepositoryImpl } from './repositories/AddonEnabledRepository';
import { AddonIndicatorClientImpl } from './client/AddonIndicatorClient'; import { AddonIndicatorClientImpl } from './client/AddonIndicatorClient';
import { AddressRepositoryImpl } from './repositories/AddressRepository';
import { ClipboardRepositoryImpl } from './repositories/ClipboardRepository'; import { ClipboardRepositoryImpl } from './repositories/ClipboardRepository';
import { ConsoleClientImpl } from './client/ConsoleClient'; import { ConsoleClientImpl } from './client/ConsoleClient';
import { ConsoleFramePresenterImpl } from './presenters/ConsoleFramePresenter'; import { ConsoleFramePresenterImpl } from './presenters/ConsoleFramePresenter';
@ -31,6 +32,7 @@ import { container } from 'tsyringe';
container.register('FollowMasterClient', { useValue: new FollowMasterClientImpl(window.top) }); container.register('FollowMasterClient', { useValue: new FollowMasterClientImpl(window.top) });
container.register('AddonEnabledRepository', { useClass: AddonEnabledRepositoryImpl }); container.register('AddonEnabledRepository', { useClass: AddonEnabledRepositoryImpl });
container.register('AddonIndicatorClient', { useClass: AddonIndicatorClientImpl }); container.register('AddonIndicatorClient', { useClass: AddonIndicatorClientImpl });
container.register('AddressRepository', { useClass: AddressRepositoryImpl });
container.register('ClipboardRepository', { useClass: ClipboardRepositoryImpl }); container.register('ClipboardRepository', { useClass: ClipboardRepositoryImpl });
container.register('ConsoleClient', { useClass: ConsoleClientImpl }); container.register('ConsoleClient', { useClass: ConsoleClientImpl });
container.register('ConsoleFramePresenter', { useClass: ConsoleFramePresenterImpl }); container.register('ConsoleFramePresenter', { useClass: ConsoleFramePresenterImpl });

@ -0,0 +1,9 @@
export default interface AddressRepository {
getCurrentURL(): URL
}
export class AddressRepositoryImpl implements AddressRepository {
getCurrentURL(): URL {
return new URL(window.location.href);
}
}

@ -6,6 +6,7 @@ import * as operations from '../../shared/operations';
import Keymaps from '../../shared/settings/Keymaps'; import Keymaps from '../../shared/settings/Keymaps';
import Key from '../../shared/settings/Key'; import Key from '../../shared/settings/Key';
import KeySequence from '../../shared/settings/KeySequence'; import KeySequence from '../../shared/settings/KeySequence';
import AddressRepository from '../repositories/AddressRepository';
type KeymapEntityMap = Map<KeySequence, operations.Operation>; type KeymapEntityMap = Map<KeySequence, operations.Operation>;
@ -25,11 +26,19 @@ export default class KeymapUseCase {
@inject('AddonEnabledRepository') @inject('AddonEnabledRepository')
private addonEnabledRepository: AddonEnabledRepository, private addonEnabledRepository: AddonEnabledRepository,
@inject('AddressRepository')
private addressRepository: AddressRepository,
) { ) {
} }
nextOp(key: Key): operations.Operation | null { nextOp(key: Key): operations.Operation | null {
let sequence = this.repository.enqueueKey(key); 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 keymaps = this.keymapEntityMap();
let matched = Array.from(keymaps.keys()).filter( let matched = Array.from(keymaps.keys()).filter(
@ -71,4 +80,10 @@ export default class KeymapUseCase {
) as [KeySequence, operations.Operation][]; ) as [KeySequence, operations.Operation][];
return new Map<KeySequence, operations.Operation>(entries); return new Map<KeySequence, operations.Operation>(entries);
} }
private blacklistKey(key: Key): boolean {
let url = this.addressRepository.getCurrentURL();
let blacklist = this.settingRepository.get().blacklist;
return blacklist.includeKey(url, key);
}
} }

@ -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: ['<S-G>'] }
],
});
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;
});
});