From 2116ac90a6dfdb0910d7ad2896f70a052aa635cc Mon Sep 17 00:00:00 2001 From: Shin'ya UEOKA Date: Sat, 5 Oct 2019 01:08:07 +0000 Subject: [PATCH] Make Search class --- src/shared/SettingData.ts | 22 +++--- src/shared/Settings.ts | 44 ++--------- src/shared/settings/Search.ts | 76 +++++++++++++++++++ src/shared/urls.ts | 4 +- .../repositories/SettingRepository.test.ts | 5 +- test/shared/SettingData.test.ts | 19 ++--- test/shared/Settings.test.ts | 66 +--------------- test/shared/settings/Search.test.ts | 68 +++++++++++++++++ test/shared/urls.test.ts | 8 +- 9 files changed, 182 insertions(+), 130 deletions(-) create mode 100644 src/shared/settings/Search.ts create mode 100644 test/shared/settings/Search.test.ts diff --git a/src/shared/SettingData.ts b/src/shared/SettingData.ts index eb83b75..6605c80 100644 --- a/src/shared/SettingData.ts +++ b/src/shared/SettingData.ts @@ -1,6 +1,7 @@ import * as operations from './operations'; import Settings, * as settings from './Settings'; import Keymaps from './settings/Keymaps'; +import Search from './settings/Search'; export class FormKeymaps { private data: {[op: string]: string}; @@ -71,15 +72,12 @@ export class FormSearch { this.engines = engines; } - toSearchSettings(): settings.Search { - return { - default: this.default, - engines: this.engines.reduce( - (o: {[key: string]: string}, [name, url]) => { - o[name] = url; - return o; - }, {}), - }; + toSearchSettings(): Search { + let engines: { [name: string]: string } = {}; + for (let entry of this.engines) { + engines[entry[0]] = entry[1]; + } + return new Search(this.default, engines); } toJSON(): { @@ -102,12 +100,12 @@ export class FormSearch { return new FormSearch(o.default, o.engines); } - static fromSearch(search: settings.Search): FormSearch { + static fromSearch(search: Search): FormSearch { let engines = Object.entries(search.engines).reduce( (o: string[][], [name, url]) => { return o.concat([[name, url]]); }, []); - return new FormSearch(search.default, engines); + return new FormSearch(search.defaultEngine, engines); } } @@ -200,7 +198,7 @@ export class FormSettings { toSettings(): Settings { return settings.valueOf({ keymaps: this.keymaps.toKeymaps().toJSON(), - search: this.search.toSearchSettings(), + search: this.search.toSearchSettings().toJSON(), properties: this.properties, blacklist: this.blacklist, }); diff --git a/src/shared/Settings.ts b/src/shared/Settings.ts index 3014abc..e2bb3f4 100644 --- a/src/shared/Settings.ts +++ b/src/shared/Settings.ts @@ -1,10 +1,6 @@ import * as PropertyDefs from './property-defs'; import Keymaps from './settings/Keymaps'; - -export interface Search { - default: string; - engines: { [key: string]: string }; -} +import Search from './settings/Search'; export interface Properties { hintchars: string; @@ -19,36 +15,6 @@ export default interface Settings { blacklist: string[]; } -export const searchValueOf = (o: any): Search => { - if (typeof o.default !== 'string') { - throw new TypeError('string field "default" not set"'); - } - for (let name of Object.keys(o.engines)) { - if ((/\s/).test(name)) { - throw new TypeError( - `While space in the search engine not allowed: "${name}"`); - } - let url = o.engines[name]; - if (typeof url !== 'string') { - throw new TypeError('"engines" not an object of string'); - } - let matches = url.match(/{}/g); - if (matches === null) { - throw new TypeError(`No {}-placeholders in URL of "${name}"`); - } else if (matches.length > 1) { - throw new TypeError(`Multiple {}-placeholders in URL of "${name}"`); - } - - } - if (!Object.prototype.hasOwnProperty.call(o.engines, o.default)) { - throw new TypeError(`Default engine "${o.default}" not found`); - } - return { - default: o.default as string, - engines: { ...o.engines }, - }; -}; - export const propertiesValueOf = (o: any): Properties => { let defNames = new Set(PropertyDefs.defs.map(def => def.name)); let unknownName = Object.keys(o).find(name => !defNames.has(name)); @@ -90,7 +56,7 @@ export const valueOf = (o: any): Settings => { settings.keymaps = Keymaps.fromJSON(o.keymaps); break; case 'search': - settings.search = searchValueOf(o.search); + settings.search = Search.fromJSON(o.search); break; case 'properties': settings.properties = propertiesValueOf(o.properties); @@ -108,7 +74,7 @@ export const valueOf = (o: any): Settings => { export const toJSON = (settings: Settings): any => { return { keymaps: settings.keymaps.toJSON(), - search: settings.search, + search: settings.search.toJSON(), properties: settings.properties, blacklist: settings.blacklist, }; @@ -179,7 +145,7 @@ export const DefaultSetting: Settings = { '.': { 'type': 'repeat.last' }, '': { 'type': 'addon.toggle.enabled' } }), - search: { + search: Search.fromJSON({ default: 'google', engines: { 'google': 'https://google.com/search?q={}', @@ -189,7 +155,7 @@ export const DefaultSetting: Settings = { 'twitter': 'https://twitter.com/search?q={}', 'wikipedia': 'https://en.wikipedia.org/w/index.php?search={}' } - }, + }), properties: { hintchars: 'abcdefghijklmnopqrstuvwxyz', smoothscroll: false, diff --git a/src/shared/settings/Search.ts b/src/shared/settings/Search.ts new file mode 100644 index 0000000..4580236 --- /dev/null +++ b/src/shared/settings/Search.ts @@ -0,0 +1,76 @@ +type Entries = { [name: string]: string }; + +export type SearchJSON = { + default: string; + engines: { [key: string]: string }; +}; + +export default class Search { + constructor( + public defaultEngine: string, + public engines: Entries, + ) { + } + + static fromJSON(json: any): Search { + let defaultEngine = Search.getStringField(json, 'default'); + let engines = Search.getObjectField(json, 'engines'); + + for (let [name, url] of Object.entries(engines)) { + if ((/\s/).test(name)) { + throw new TypeError( + `While space in the search engine not allowed: "${name}"`); + } + if (typeof url !== 'string') { + throw new TypeError( + `Invalid type of value in filed "engines": ${JSON.stringify(json)}`); + } + let matches = url.match(/{}/g); + if (matches === null) { + throw new TypeError(`No {}-placeholders in URL of "${name}"`); + } else if (matches.length > 1) { + throw new TypeError(`Multiple {}-placeholders in URL of "${name}"`); + } + } + + if (!Object.keys(engines).includes(defaultEngine)) { + throw new TypeError(`Default engine "${defaultEngine}" not found`); + } + + return new Search( + json.default as string, + json.engines, + ); + } + + toJSON(): SearchJSON { + return { + default: this.defaultEngine, + engines: this.engines, + }; + } + + private static getStringField(json: any, name: string): string { + if (!Object.prototype.hasOwnProperty.call(json, name)) { + throw new TypeError( + `missing field "${name}" on search: ${JSON.stringify(json)}`); + } + if (typeof json[name] !== 'string') { + throw new TypeError( + `invalid type of filed "${name}" on search: ${JSON.stringify(json)}`); + } + return json[name]; + } + + private static getObjectField(json: any, name: string): Object { + if (!Object.prototype.hasOwnProperty.call(json, name)) { + throw new TypeError( + `missing field "${name}" on search: ${JSON.stringify(json)}`); + } + if (typeof json[name] !== 'object' || json[name] === null) { + throw new TypeError( + `invalid type of filed "${name}" on search: ${JSON.stringify(json)}`); + } + return json[name]; + } +} diff --git a/src/shared/urls.ts b/src/shared/urls.ts index bbdb1ea..64ea4f2 100644 --- a/src/shared/urls.ts +++ b/src/shared/urls.ts @@ -1,4 +1,4 @@ -import { Search } from './Settings'; +import Search from './settings/Search'; const trimStart = (str: string): string => { // NOTE String.trimStart is available on Firefox 61 @@ -19,7 +19,7 @@ const searchUrl = (keywords: string, search: Search): string => { if (keywords.includes('.') && !keywords.includes(' ')) { return 'http://' + keywords; } - let template = search.engines[search.default]; + let template = search.engines[search.defaultEngine]; let query = keywords; let first = trimStart(keywords).split(' ')[0]; diff --git a/test/content/repositories/SettingRepository.test.ts b/test/content/repositories/SettingRepository.test.ts index 457ca4c..363fcec 100644 --- a/test/content/repositories/SettingRepository.test.ts +++ b/test/content/repositories/SettingRepository.test.ts @@ -1,6 +1,7 @@ import { SettingRepositoryImpl } from '../../../src/content/repositories/SettingRepository'; import { expect } from 'chai'; import Keymaps from '../../../src/shared/settings/Keymaps'; +import Search from '../../../src/shared/settings/Search'; describe('SettingRepositoryImpl', () => { it('updates and gets current value', () => { @@ -8,12 +9,12 @@ describe('SettingRepositoryImpl', () => { let settings = { keymaps: Keymaps.fromJSON({}), - search: { + search: Search.fromJSON({ default: 'google', engines: { google: 'https://google.com/?q={}', } - }, + }), properties: { hintchars: 'abcd1234', smoothscroll: false, diff --git a/test/shared/SettingData.test.ts b/test/shared/SettingData.test.ts index 9567f76..f8995d9 100644 --- a/test/shared/SettingData.test.ts +++ b/test/shared/SettingData.test.ts @@ -4,6 +4,7 @@ import SettingData, { import Settings from '../../src/shared/Settings'; import { expect } from 'chai'; import Keymaps from '../../src/shared/settings/Keymaps'; +import Search from '../../src/shared/settings/Search'; describe('shared/SettingData', () => { describe('FormKeymaps', () => { @@ -60,7 +61,7 @@ describe('shared/SettingData', () => { let settings = JSONTextSettings.fromText(o).toSettings(); expect({ keymaps: settings.keymaps.toJSON(), - search: settings.search, + search: settings.search.toJSON(), properties: settings.properties, blacklist: settings.blacklist, }).to.deep.equal(JSON.parse(o)); @@ -71,12 +72,12 @@ describe('shared/SettingData', () => { it('create from a Settings and create a JSON string', () => { let o = { keymaps: Keymaps.fromJSON({}), - search: { + search: Search.fromJSON({ default: "google", engines: { google: "https://google.com/search?q={}", }, - }, + }), properties: { hintchars: "abcdefghijklmnopqrstuvwxyz", smoothscroll: false, @@ -88,7 +89,7 @@ describe('shared/SettingData', () => { let json = JSONTextSettings.fromSettings(o).toJSONText(); expect(JSON.parse(json)).to.deep.equal({ keymaps: o.keymaps.toJSON(), - search: o.search, + search: o.search.toJSON(), properties: o.properties, blacklist: o.blacklist, }); @@ -121,7 +122,7 @@ describe('shared/SettingData', () => { let settings = FormSettings.valueOf(data).toSettings(); expect({ keymaps: settings.keymaps.toJSON(), - search: settings.search, + search: settings.search.toJSON(), properties: settings.properties, blacklist: settings.blacklist, }).to.deep.equal({ @@ -152,12 +153,12 @@ describe('shared/SettingData', () => { 'j': { type: 'scroll.vertically', count: 1 }, '0': { type: 'scroll.home' }, }), - search: { + search: Search.fromJSON({ default: "google", engines: { "google": "https://google.com/search?q={}" } - }, + }), properties: { hintchars: "abcdefghijklmnopqrstuvwxyz", smoothscroll: false, @@ -278,7 +279,7 @@ describe('shared/SettingData', () => { }; let settings = SettingData.valueOf(data).toSettings(); - expect(settings.search.default).to.equal('google'); + expect(settings.search.defaultEngine).to.equal('google'); }); it('parse object from form source', () => { @@ -302,7 +303,7 @@ describe('shared/SettingData', () => { }; let settings = SettingData.valueOf(data).toSettings(); - expect(settings.search.default).to.equal('yahoo'); + expect(settings.search.defaultEngine).to.equal('yahoo'); }); }); }); diff --git a/test/shared/Settings.test.ts b/test/shared/Settings.test.ts index 9faf9d1..ed791a1 100644 --- a/test/shared/Settings.test.ts +++ b/test/shared/Settings.test.ts @@ -1,67 +1,7 @@ import * as settings from '../../src/shared/Settings'; -import {expect} from 'chai'; +import { expect } from 'chai'; describe('Settings', () => { - describe('#searchValueOf', () => { - it('returns search settings by valid settings', () => { - let search = settings.searchValueOf({ - default: "google", - engines: { - "google": "https://google.com/search?q={}", - "yahoo": "https://search.yahoo.com/search?p={}", - } - }); - - expect(search).to.deep.equal({ - default: "google", - engines: { - "google": "https://google.com/search?q={}", - "yahoo": "https://search.yahoo.com/search?p={}", - } - }); - }); - - it('throws a TypeError by invalid settings', () => { - expect(() => settings.searchValueOf(null)).to.throw(TypeError); - expect(() => settings.searchValueOf({})).to.throw(TypeError); - expect(() => settings.searchValueOf([])).to.throw(TypeError); - expect(() => settings.searchValueOf({ - default: 123, - engines: {} - })).to.throw(TypeError); - expect(() => settings.searchValueOf({ - default: "google", - engines: { - "google": 123456, - } - })).to.throw(TypeError); - expect(() => settings.searchValueOf({ - default: "wikipedia", - engines: { - "google": "https://google.com/search?q={}", - "yahoo": "https://search.yahoo.com/search?p={}", - } - })).to.throw(TypeError); - expect(() => settings.searchValueOf({ - default: "g o o g l e", - engines: { - "g o o g l e": "https://google.com/search?q={}", - } - })).to.throw(TypeError); - expect(() => settings.searchValueOf({ - default: "google", - engines: { - "google": "https://google.com/search", - } - })).to.throw(TypeError); - expect(() => settings.searchValueOf({ - default: "google", - engines: { - "google": "https://google.com/search?q={}&r={}", - } - })).to.throw(TypeError); - }); - }); describe('#propertiesValueOf', () => { it('returns with default properties by empty settings', () => { @@ -129,7 +69,7 @@ describe('Settings', () => { expect({ keymaps: x.keymaps.toJSON(), - search: x.search, + search: x.search.toJSON(), properties: x.properties, blacklist: x.blacklist, }).to.deep.equal({ @@ -153,7 +93,7 @@ describe('Settings', () => { let value = settings.valueOf({}); expect(value.keymaps.toJSON()).to.not.be.empty; expect(value.properties).to.not.be.empty; - expect(value.search.default).to.be.a('string'); + expect(value.search.defaultEngine).to.be.a('string'); expect(value.search.engines).to.be.an('object'); expect(value.blacklist).to.be.empty; }); diff --git a/test/shared/settings/Search.test.ts b/test/shared/settings/Search.test.ts new file mode 100644 index 0000000..7c9134d --- /dev/null +++ b/test/shared/settings/Search.test.ts @@ -0,0 +1,68 @@ +import Search from '../../../src/shared/settings/Search'; +import { expect } from 'chai'; + +describe('Search', () => { + it('returns search settings by valid settings', () => { + let search = Search.fromJSON({ + default: 'google', + engines: { + 'google': 'https://google.com/search?q={}', + 'yahoo': 'https://search.yahoo.com/search?p={}', + } + }); + + expect(search.defaultEngine).to.equal('google') + expect(search.engines).to.deep.equals({ + 'google': 'https://google.com/search?q={}', + 'yahoo': 'https://search.yahoo.com/search?p={}', + }); + expect(search.toJSON()).to.deep.equal({ + default: 'google', + engines: { + 'google': 'https://google.com/search?q={}', + 'yahoo': 'https://search.yahoo.com/search?p={}', + } + }); + }); + + it('throws a TypeError by invalid settings', () => { + expect(() => Search.fromJSON(null)).to.throw(TypeError); + expect(() => Search.fromJSON({})).to.throw(TypeError); + expect(() => Search.fromJSON([])).to.throw(TypeError); + expect(() => Search.fromJSON({ + default: 123, + engines: {} + })).to.throw(TypeError); + expect(() => Search.fromJSON({ + default: 'google', + engines: { + 'google': 123456, + } + })).to.throw(TypeError); + expect(() => Search.fromJSON({ + default: 'wikipedia', + engines: { + 'google': 'https://google.com/search?q={}', + 'yahoo': 'https://search.yahoo.com/search?p={}', + } + })).to.throw(TypeError); + expect(() => Search.fromJSON({ + default: 'g o o g l e', + engines: { + 'g o o g l e': 'https://google.com/search?q={}', + } + })).to.throw(TypeError); + expect(() => Search.fromJSON({ + default: 'google', + engines: { + 'google': 'https://google.com/search', + } + })).to.throw(TypeError); + expect(() => Search.fromJSON({ + default: 'google', + engines: { + 'google': 'https://google.com/search?q={}&r={}', + } + })).to.throw(TypeError); + }); +}); diff --git a/test/shared/urls.test.ts b/test/shared/urls.test.ts index f2950b6..3a3eea6 100644 --- a/test/shared/urls.test.ts +++ b/test/shared/urls.test.ts @@ -1,14 +1,16 @@ -import * as parsers from 'shared/urls'; +import * as parsers from '../../src/shared/urls'; +import { expect } from 'chai'; +import Search from '../../src/shared/settings/Search'; describe("shared/commands/parsers", () => { describe('#searchUrl', () => { - const config = { + const config = Search.fromJSON({ default: 'google', engines: { google: 'https://google.com/search?q={}', yahoo: 'https://yahoo.com/search?q={}', } - }; + }); it('convertes search url', () => { expect(parsers.searchUrl('google.com', config))