Make Search class
This commit is contained in:
		
							parent
							
								
									410ffbb037
								
							
						
					
					
						commit
						2116ac90a6
					
				
					 9 changed files with 182 additions and 130 deletions
				
			
		|  | @ -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, | ||||
|     }); | ||||
|  |  | |||
|  | @ -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' }, | ||||
|     '<S-Esc>': { '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, | ||||
|  |  | |||
							
								
								
									
										76
									
								
								src/shared/settings/Search.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								src/shared/settings/Search.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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]; | ||||
|   } | ||||
| } | ||||
|  | @ -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]; | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -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'); | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
|  |  | |||
|  | @ -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; | ||||
|     }); | ||||
|  |  | |||
							
								
								
									
										68
									
								
								test/shared/settings/Search.test.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								test/shared/settings/Search.test.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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); | ||||
|   }); | ||||
| }); | ||||
|  | @ -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)) | ||||
|  |  | |||
		Reference in a new issue