From 9ff80fcac3600401c9fed053cc8422f89c404940 Mon Sep 17 00:00:00 2001 From: Shin'ya UEOKA Date: Sat, 5 Oct 2019 08:52:49 +0000 Subject: Add partial blacklist item --- test/shared/settings/Blacklist.test.ts | 178 ++++++++++++++++++++++++--------- 1 file changed, 128 insertions(+), 50 deletions(-) (limited to 'test/shared/settings') 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; + }); + }); }); -- cgit v1.2.3 From fa6dfb0395826041349c604edcbcbaa316fc95d8 Mon Sep 17 00:00:00 2001 From: Shin'ya UEOKA Date: Sun, 6 Oct 2019 12:51:43 +0000 Subject: Add partial blacklist form --- src/settings/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 (limited to 'test/shared/settings') 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; }); }); }); -- cgit v1.2.3