commit
f5dfdb0bd7
25 changed files with 691 additions and 201 deletions
@ -0,0 +1,79 @@ |
|||||||
|
import actions from '../actions'; |
||||||
|
import * as tabs from 'background/tabs'; |
||||||
|
import * as parsers from 'shared/commands/parsers'; |
||||||
|
import * as properties from 'shared/settings/properties'; |
||||||
|
|
||||||
|
const openCommand = (url) => { |
||||||
|
return browser.tabs.query({ |
||||||
|
active: true, currentWindow: true |
||||||
|
}).then((gotTabs) => { |
||||||
|
if (gotTabs.length > 0) { |
||||||
|
return browser.tabs.update(gotTabs[0].id, { url: url }); |
||||||
|
} |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const tabopenCommand = (url) => { |
||||||
|
return browser.tabs.create({ url: url }); |
||||||
|
}; |
||||||
|
|
||||||
|
const winopenCommand = (url) => { |
||||||
|
return browser.windows.create({ url }); |
||||||
|
}; |
||||||
|
|
||||||
|
const bufferCommand = (keywords) => { |
||||||
|
if (keywords.length === 0) { |
||||||
|
return Promise.resolve([]); |
||||||
|
} |
||||||
|
let keywordsStr = keywords.join(' '); |
||||||
|
return browser.tabs.query({ |
||||||
|
active: true, currentWindow: true |
||||||
|
}).then((gotTabs) => { |
||||||
|
if (gotTabs.length > 0) { |
||||||
|
if (isNaN(keywordsStr)) { |
||||||
|
return tabs.selectByKeyword(gotTabs[0], keywordsStr); |
||||||
|
} |
||||||
|
let index = parseInt(keywordsStr, 10) - 1; |
||||||
|
return tabs.selectAt(index); |
||||||
|
} |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const setCommand = (args) => { |
||||||
|
if (!args[0]) { |
||||||
|
return Promise.resolve(); |
||||||
|
} |
||||||
|
|
||||||
|
let [name, value] = parsers.parseSetOption(args[0], properties.types); |
||||||
|
return { |
||||||
|
type: actions.SETTING_SET_PROPERTY, |
||||||
|
name, |
||||||
|
value |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
const exec = (line, settings) => { |
||||||
|
let [name, args] = parsers.parseCommandLine(line); |
||||||
|
|
||||||
|
switch (name) { |
||||||
|
case 'o': |
||||||
|
case 'open': |
||||||
|
return openCommand(parsers.normalizeUrl(args, settings.search)); |
||||||
|
case 't': |
||||||
|
case 'tabopen': |
||||||
|
return tabopenCommand(parsers.normalizeUrl(args, settings.search)); |
||||||
|
case 'w': |
||||||
|
case 'winopen': |
||||||
|
return winopenCommand(parsers.normalizeUrl(args, settings.search)); |
||||||
|
case 'b': |
||||||
|
case 'buffer': |
||||||
|
return bufferCommand(args); |
||||||
|
case 'set': |
||||||
|
return setCommand(args); |
||||||
|
case '': |
||||||
|
return Promise.resolve(); |
||||||
|
} |
||||||
|
throw new Error(name + ' command is not defined'); |
||||||
|
}; |
||||||
|
|
||||||
|
export { exec }; |
@ -0,0 +1,5 @@ |
|||||||
|
export default { |
||||||
|
// Settings
|
||||||
|
SETTING_SET_SETTINGS: 'setting.set.settings', |
||||||
|
SETTING_SET_PROPERTY: 'setting.set.property', |
||||||
|
}; |
@ -0,0 +1,21 @@ |
|||||||
|
import actions from '../actions'; |
||||||
|
import * as settingsStorage from 'shared/settings/storage'; |
||||||
|
|
||||||
|
const load = () => { |
||||||
|
return settingsStorage.loadValue().then((value) => { |
||||||
|
return { |
||||||
|
type: actions.SETTING_SET_SETTINGS, |
||||||
|
value, |
||||||
|
}; |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const setProperty = (name, value) => { |
||||||
|
return { |
||||||
|
type: actions.SETTING_SET_PROPERTY, |
||||||
|
name, |
||||||
|
value, |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
export { load, setProperty }; |
@ -0,0 +1,24 @@ |
|||||||
|
import actions from 'background/actions'; |
||||||
|
|
||||||
|
const defaultState = { |
||||||
|
value: {}, |
||||||
|
}; |
||||||
|
|
||||||
|
export default function reducer(state = defaultState, action = {}) { |
||||||
|
switch (action.type) { |
||||||
|
case actions.SETTING_SET_SETTINGS: |
||||||
|
return { |
||||||
|
value: action.value, |
||||||
|
}; |
||||||
|
case actions.SETTING_SET_PROPERTY: |
||||||
|
return { |
||||||
|
value: Object.assign({}, state.value, { |
||||||
|
properties: Object.assign({}, state.value.properties, |
||||||
|
{ [action.name]: action.value }) |
||||||
|
}) |
||||||
|
}; |
||||||
|
default: |
||||||
|
return state; |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,60 @@ |
|||||||
|
import './properties-form.scss'; |
||||||
|
import { h, Component } from 'preact'; |
||||||
|
|
||||||
|
class PropertiesForm extends Component { |
||||||
|
|
||||||
|
render() { |
||||||
|
let types = this.props.types; |
||||||
|
let value = this.props.value; |
||||||
|
if (!value) { |
||||||
|
value = {}; |
||||||
|
} |
||||||
|
|
||||||
|
return <div className='form-properties-form'> |
||||||
|
{ |
||||||
|
Object.keys(types).map((name) => { |
||||||
|
let type = types[name]; |
||||||
|
let inputType = null; |
||||||
|
if (type === 'string') { |
||||||
|
inputType = 'text'; |
||||||
|
} else if (type === 'number') { |
||||||
|
inputType = 'number'; |
||||||
|
} else if (type === 'boolean') { |
||||||
|
inputType = 'checkbox'; |
||||||
|
} |
||||||
|
return <div key={name} className='form-properties-form-row'> |
||||||
|
<label> |
||||||
|
<span className='column-name'>{name}</span> |
||||||
|
<input type={inputType} name={name} |
||||||
|
className='column-input' |
||||||
|
value={value[name] ? value[name] : ''} |
||||||
|
onChange={this.bindValue.bind(this)} |
||||||
|
checked={value[name]} |
||||||
|
/> |
||||||
|
</label> |
||||||
|
</div>; |
||||||
|
}) |
||||||
|
} |
||||||
|
</div>; |
||||||
|
} |
||||||
|
|
||||||
|
bindValue(e) { |
||||||
|
if (!this.props.onChange) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
let name = e.target.name; |
||||||
|
let next = Object.assign({}, this.props.value); |
||||||
|
if (e.target.type.toLowerCase() === 'checkbox') { |
||||||
|
next[name] = e.target.checked; |
||||||
|
} else if (e.target.type.toLowerCase() === 'number') { |
||||||
|
next[name] = Number(e.target.value); |
||||||
|
} else { |
||||||
|
next[name] = e.target.value; |
||||||
|
} |
||||||
|
|
||||||
|
this.props.onChange(next); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default PropertiesForm; |
@ -0,0 +1,12 @@ |
|||||||
|
.form-properties-form { |
||||||
|
&-row { |
||||||
|
.column-name { |
||||||
|
display: inline-block; |
||||||
|
min-width: 5rem; |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
.column-input { |
||||||
|
line-height: 2.2rem; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -1,169 +0,0 @@ |
|||||||
import * as tabs from 'background/tabs'; |
|
||||||
import * as histories from 'background/histories'; |
|
||||||
|
|
||||||
const normalizeUrl = (args, searchConfig) => { |
|
||||||
let concat = args.join(' '); |
|
||||||
try { |
|
||||||
return new URL(concat).href; |
|
||||||
} catch (e) { |
|
||||||
if (concat.includes('.') && !concat.includes(' ')) { |
|
||||||
return 'http://' + concat; |
|
||||||
} |
|
||||||
let query = concat; |
|
||||||
let template = searchConfig.engines[ |
|
||||||
searchConfig.default |
|
||||||
]; |
|
||||||
for (let key in searchConfig.engines) { |
|
||||||
if (args[0] === key) { |
|
||||||
query = args.slice(1).join(' '); |
|
||||||
template = searchConfig.engines[key]; |
|
||||||
} |
|
||||||
} |
|
||||||
return template.replace('{}', encodeURIComponent(query)); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
const openCommand = (url) => { |
|
||||||
return browser.tabs.query({ |
|
||||||
active: true, currentWindow: true |
|
||||||
}).then((gotTabs) => { |
|
||||||
if (gotTabs.length > 0) { |
|
||||||
return browser.tabs.update(gotTabs[0].id, { url: url }); |
|
||||||
} |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
const tabopenCommand = (url) => { |
|
||||||
return browser.tabs.create({ url: url }); |
|
||||||
}; |
|
||||||
|
|
||||||
const winopenCommand = (url) => { |
|
||||||
return browser.windows.create({ url }); |
|
||||||
}; |
|
||||||
|
|
||||||
const bufferCommand = (keywords) => { |
|
||||||
if (keywords.length === 0) { |
|
||||||
return Promise.resolve([]); |
|
||||||
} |
|
||||||
let keywordsStr = keywords.join(' '); |
|
||||||
return browser.tabs.query({ |
|
||||||
active: true, currentWindow: true |
|
||||||
}).then((gotTabs) => { |
|
||||||
if (gotTabs.length > 0) { |
|
||||||
if (isNaN(keywordsStr)) { |
|
||||||
return tabs.selectByKeyword(gotTabs[0], keywordsStr); |
|
||||||
} |
|
||||||
let index = parseInt(keywordsStr, 10) - 1; |
|
||||||
return tabs.selectAt(index); |
|
||||||
} |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
const getOpenCompletions = (command, keywords, searchConfig) => { |
|
||||||
return histories.getCompletions(keywords).then((pages) => { |
|
||||||
let historyItems = pages.map((page) => { |
|
||||||
return { |
|
||||||
caption: page.title, |
|
||||||
content: command + ' ' + page.url, |
|
||||||
url: page.url |
|
||||||
}; |
|
||||||
}); |
|
||||||
let engineNames = Object.keys(searchConfig.engines); |
|
||||||
let engineItems = engineNames.filter(name => name.startsWith(keywords)) |
|
||||||
.map(name => ({ |
|
||||||
caption: name, |
|
||||||
content: command + ' ' + name |
|
||||||
})); |
|
||||||
|
|
||||||
let completions = []; |
|
||||||
if (engineItems.length > 0) { |
|
||||||
completions.push({ |
|
||||||
name: 'Search Engines', |
|
||||||
items: engineItems |
|
||||||
}); |
|
||||||
} |
|
||||||
if (historyItems.length > 0) { |
|
||||||
completions.push({ |
|
||||||
name: 'History', |
|
||||||
items: historyItems |
|
||||||
}); |
|
||||||
} |
|
||||||
return completions; |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
const doCommand = (line, settings) => { |
|
||||||
let words = line.trim().split(/ +/); |
|
||||||
let name = words.shift(); |
|
||||||
|
|
||||||
switch (name) { |
|
||||||
case 'o': |
|
||||||
case 'open': |
|
||||||
return openCommand(normalizeUrl(words, settings.search)); |
|
||||||
case 't': |
|
||||||
case 'tabopen': |
|
||||||
return tabopenCommand(normalizeUrl(words, settings.search)); |
|
||||||
case 'w': |
|
||||||
case 'winopen': |
|
||||||
return winopenCommand(normalizeUrl(words, settings.search)); |
|
||||||
case 'b': |
|
||||||
case 'buffer': |
|
||||||
return bufferCommand(words); |
|
||||||
case '': |
|
||||||
return Promise.resolve(); |
|
||||||
} |
|
||||||
throw new Error(name + ' command is not defined'); |
|
||||||
}; |
|
||||||
|
|
||||||
const getCompletions = (line, settings) => { |
|
||||||
let typedWords = line.trim().split(/ +/); |
|
||||||
let typing = ''; |
|
||||||
if (!line.endsWith(' ')) { |
|
||||||
typing = typedWords.pop(); |
|
||||||
} |
|
||||||
|
|
||||||
if (typedWords.length === 0) { |
|
||||||
return Promise.resolve([]); |
|
||||||
} |
|
||||||
let name = typedWords.shift(); |
|
||||||
let keywords = typedWords.concat(typing).join(' '); |
|
||||||
|
|
||||||
switch (name) { |
|
||||||
case 'o': |
|
||||||
case 'open': |
|
||||||
case 't': |
|
||||||
case 'tabopen': |
|
||||||
case 'w': |
|
||||||
case 'winopen': |
|
||||||
return getOpenCompletions(name, keywords, settings.search); |
|
||||||
case 'b': |
|
||||||
case 'buffer': |
|
||||||
return tabs.getCompletions(keywords).then((gotTabs) => { |
|
||||||
let items = gotTabs.map((tab) => { |
|
||||||
return { |
|
||||||
caption: tab.title, |
|
||||||
content: name + ' ' + tab.title, |
|
||||||
url: tab.url, |
|
||||||
icon: tab.favIconUrl |
|
||||||
}; |
|
||||||
}); |
|
||||||
return [ |
|
||||||
{ |
|
||||||
name: 'Buffers', |
|
||||||
items: items |
|
||||||
} |
|
||||||
]; |
|
||||||
}); |
|
||||||
} |
|
||||||
return Promise.resolve([]); |
|
||||||
}; |
|
||||||
|
|
||||||
const exec = (line, settings) => { |
|
||||||
return doCommand(line, settings); |
|
||||||
}; |
|
||||||
|
|
||||||
const complete = (line, settings) => { |
|
||||||
return getCompletions(line, settings); |
|
||||||
}; |
|
||||||
|
|
||||||
export { exec, complete }; |
|
@ -0,0 +1,84 @@ |
|||||||
|
import * as tabs from 'background/tabs'; |
||||||
|
import * as histories from 'background/histories'; |
||||||
|
|
||||||
|
const getOpenCompletions = (command, keywords, searchConfig) => { |
||||||
|
return histories.getCompletions(keywords).then((pages) => { |
||||||
|
let historyItems = pages.map((page) => { |
||||||
|
return { |
||||||
|
caption: page.title, |
||||||
|
content: command + ' ' + page.url, |
||||||
|
url: page.url |
||||||
|
}; |
||||||
|
}); |
||||||
|
let engineNames = Object.keys(searchConfig.engines); |
||||||
|
let engineItems = engineNames.filter(name => name.startsWith(keywords)) |
||||||
|
.map(name => ({ |
||||||
|
caption: name, |
||||||
|
content: command + ' ' + name |
||||||
|
})); |
||||||
|
|
||||||
|
let completions = []; |
||||||
|
if (engineItems.length > 0) { |
||||||
|
completions.push({ |
||||||
|
name: 'Search Engines', |
||||||
|
items: engineItems |
||||||
|
}); |
||||||
|
} |
||||||
|
if (historyItems.length > 0) { |
||||||
|
completions.push({ |
||||||
|
name: 'History', |
||||||
|
items: historyItems |
||||||
|
}); |
||||||
|
} |
||||||
|
return completions; |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const getCompletions = (line, settings) => { |
||||||
|
let typedWords = line.trim().split(/ +/); |
||||||
|
let typing = ''; |
||||||
|
if (!line.endsWith(' ')) { |
||||||
|
typing = typedWords.pop(); |
||||||
|
} |
||||||
|
|
||||||
|
if (typedWords.length === 0) { |
||||||
|
return Promise.resolve([]); |
||||||
|
} |
||||||
|
let name = typedWords.shift(); |
||||||
|
let keywords = typedWords.concat(typing).join(' '); |
||||||
|
|
||||||
|
switch (name) { |
||||||
|
case 'o': |
||||||
|
case 'open': |
||||||
|
case 't': |
||||||
|
case 'tabopen': |
||||||
|
case 'w': |
||||||
|
case 'winopen': |
||||||
|
return getOpenCompletions(name, keywords, settings.search); |
||||||
|
case 'b': |
||||||
|
case 'buffer': |
||||||
|
return tabs.getCompletions(keywords).then((gotTabs) => { |
||||||
|
let items = gotTabs.map((tab) => { |
||||||
|
return { |
||||||
|
caption: tab.title, |
||||||
|
content: name + ' ' + tab.title, |
||||||
|
url: tab.url, |
||||||
|
icon: tab.favIconUrl |
||||||
|
}; |
||||||
|
}); |
||||||
|
return [ |
||||||
|
{ |
||||||
|
name: 'Buffers', |
||||||
|
items: items |
||||||
|
} |
||||||
|
]; |
||||||
|
}); |
||||||
|
} |
||||||
|
return Promise.resolve([]); |
||||||
|
}; |
||||||
|
|
||||||
|
const complete = (line, settings) => { |
||||||
|
return getCompletions(line, settings); |
||||||
|
}; |
||||||
|
|
||||||
|
export default complete; |
@ -0,0 +1,3 @@ |
|||||||
|
import complete from './complete'; |
||||||
|
|
||||||
|
export { complete }; |
@ -0,0 +1,59 @@ |
|||||||
|
const normalizeUrl = (args, searchConfig) => { |
||||||
|
let concat = args.join(' '); |
||||||
|
try { |
||||||
|
return new URL(concat).href; |
||||||
|
} catch (e) { |
||||||
|
if (concat.includes('.') && !concat.includes(' ')) { |
||||||
|
return 'http://' + concat; |
||||||
|
} |
||||||
|
let query = concat; |
||||||
|
let template = searchConfig.engines[ |
||||||
|
searchConfig.default |
||||||
|
]; |
||||||
|
for (let key in searchConfig.engines) { |
||||||
|
if (args[0] === key) { |
||||||
|
query = args.slice(1).join(' '); |
||||||
|
template = searchConfig.engines[key]; |
||||||
|
} |
||||||
|
} |
||||||
|
return template.replace('{}', encodeURIComponent(query)); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const mustNumber = (v) => { |
||||||
|
let num = Number(v); |
||||||
|
if (isNaN(num)) { |
||||||
|
throw new Error('Not number: ' + v); |
||||||
|
} |
||||||
|
return num; |
||||||
|
}; |
||||||
|
|
||||||
|
const parseSetOption = (word, types) => { |
||||||
|
let [key, value] = word.split('='); |
||||||
|
if (value === undefined) { |
||||||
|
value = !key.startsWith('no'); |
||||||
|
key = value ? key : key.slice(2); |
||||||
|
} |
||||||
|
let type = types[key]; |
||||||
|
if (!type) { |
||||||
|
throw new Error('Unknown property: ' + key); |
||||||
|
} |
||||||
|
if (type === 'boolean' && typeof value !== 'boolean' || |
||||||
|
type !== 'boolean' && typeof value === 'boolean') { |
||||||
|
throw new Error('Invalid argument: ' + word); |
||||||
|
} |
||||||
|
|
||||||
|
switch (type) { |
||||||
|
case 'string': return [key, value]; |
||||||
|
case 'number': return [key, mustNumber(value)]; |
||||||
|
case 'boolean': return [key, value]; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const parseCommandLine = (line) => { |
||||||
|
let words = line.trim().split(/ +/); |
||||||
|
let name = words.shift(); |
||||||
|
return [name, words]; |
||||||
|
}; |
||||||
|
|
||||||
|
export { normalizeUrl, parseCommandLine, parseSetOption }; |
@ -0,0 +1,15 @@ |
|||||||
|
const types = { |
||||||
|
// TODO describe property types here
|
||||||
|
// mystr: 'string',
|
||||||
|
// mynum: 'number',
|
||||||
|
// mybool: 'boolean',
|
||||||
|
}; |
||||||
|
|
||||||
|
const defaults = { |
||||||
|
// TODO describe property defaults values
|
||||||
|
// mystr: 'hello',
|
||||||
|
// mynum: 123,
|
||||||
|
// mybool: true,
|
||||||
|
}; |
||||||
|
|
||||||
|
export { types, defaults }; |
@ -0,0 +1,31 @@ |
|||||||
|
import DefaultSettings from './default'; |
||||||
|
import * as settingsValues from './values'; |
||||||
|
|
||||||
|
const loadRaw = () => { |
||||||
|
return browser.storage.local.get('settings').then(({ settings }) => { |
||||||
|
if (!settings) { |
||||||
|
return DefaultSettings; |
||||||
|
} |
||||||
|
return Object.assign({}, DefaultSettings, settings); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const loadValue = () => { |
||||||
|
return loadRaw().then((settings) => { |
||||||
|
let value = JSON.parse(DefaultSettings.json); |
||||||
|
if (settings.source === 'json') { |
||||||
|
value = settingsValues.valueFromJson(settings.json); |
||||||
|
} else if (settings.source === 'form') { |
||||||
|
value = settingsValues.valueFromForm(settings.form); |
||||||
|
} |
||||||
|
return value; |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const save = (settings) => { |
||||||
|
return browser.storage.local.set({ |
||||||
|
settings, |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
export { loadRaw, loadValue, save }; |
@ -0,0 +1,37 @@ |
|||||||
|
import { expect } from "chai"; |
||||||
|
import actions from 'background/actions'; |
||||||
|
import settingReducer from 'background/reducers/setting'; |
||||||
|
|
||||||
|
describe("setting reducer", () => { |
||||||
|
it('return the initial state', () => { |
||||||
|
let state = settingReducer(undefined, {}); |
||||||
|
expect(state).to.have.deep.property('value', {}); |
||||||
|
}); |
||||||
|
|
||||||
|
it('return next state for SETTING_SET_SETTINGS', () => { |
||||||
|
let action = { |
||||||
|
type: actions.SETTING_SET_SETTINGS, |
||||||
|
value: { key: 123 }, |
||||||
|
}; |
||||||
|
let state = settingReducer(undefined, action); |
||||||
|
expect(state).to.have.deep.property('value', { key: 123 }); |
||||||
|
}); |
||||||
|
|
||||||
|
it('return next state for SETTING_SET_PROPERTY', () => { |
||||||
|
let state = { |
||||||
|
value: { |
||||||
|
properties: { smoothscroll: true } |
||||||
|
} |
||||||
|
} |
||||||
|
let action = { |
||||||
|
type: actions.SETTING_SET_PROPERTY, |
||||||
|
name: 'encoding', |
||||||
|
value: 'utf-8', |
||||||
|
}; |
||||||
|
state = settingReducer(state, action); |
||||||
|
|
||||||
|
console.log(state); |
||||||
|
expect(state.value.properties).to.have.property('smoothscroll', true); |
||||||
|
expect(state.value.properties).to.have.property('encoding', 'utf-8'); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,86 @@ |
|||||||
|
import { expect } from 'chai'; |
||||||
|
import { h, render } from 'preact'; |
||||||
|
import PropertiesForm from 'settings/components/form/properties-form' |
||||||
|
|
||||||
|
describe("settings/form/PropertiesForm", () => { |
||||||
|
beforeEach(() => { |
||||||
|
document.body.innerHTML = ''; |
||||||
|
}); |
||||||
|
|
||||||
|
describe('render', () => { |
||||||
|
it('renders PropertiesForm', () => { |
||||||
|
let types = { |
||||||
|
mystr: 'string', |
||||||
|
mynum: 'number', |
||||||
|
mybool: 'boolean', |
||||||
|
empty: 'string', |
||||||
|
} |
||||||
|
let value = { |
||||||
|
mystr: 'abc', |
||||||
|
mynum: 123, |
||||||
|
mybool: true, |
||||||
|
}; |
||||||
|
render(<PropertiesForm types={types} value={value} />, document.body); |
||||||
|
|
||||||
|
let strInput = document.querySelector('input[name=mystr]'); |
||||||
|
let numInput = document.querySelector('input[name=mynum]'); |
||||||
|
let boolInput = document.querySelector('input[name=mybool]'); |
||||||
|
let emptyInput = document.querySelector('input[name=empty]'); |
||||||
|
|
||||||
|
expect(strInput.type).to.equals('text'); |
||||||
|
expect(strInput.value).to.equal('abc'); |
||||||
|
expect(numInput.type).to.equals('number'); |
||||||
|
expect(numInput.value).to.equal('123'); |
||||||
|
expect(boolInput.type).to.equals('checkbox'); |
||||||
|
expect(boolInput.checked).to.be.true; |
||||||
|
expect(emptyInput.type).to.equals('text'); |
||||||
|
expect(emptyInput.value).to.be.empty; |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('onChange', () => { |
||||||
|
it('invokes onChange event on text changed', (done) => { |
||||||
|
render(<PropertiesForm |
||||||
|
types={{ 'myvalue': 'string' }} |
||||||
|
value={{ 'myvalue': 'abc' }} |
||||||
|
onChange={value => { |
||||||
|
expect(value).to.have.property('myvalue', 'abcd'); |
||||||
|
done(); |
||||||
|
}} |
||||||
|
/>, document.body); |
||||||
|
|
||||||
|
let input = document.querySelector('input[name=myvalue]'); |
||||||
|
input.value = 'abcd' |
||||||
|
input.dispatchEvent(new Event('change')) |
||||||
|
}); |
||||||
|
|
||||||
|
it('invokes onChange event on number changeed', (done) => { |
||||||
|
render(<PropertiesForm |
||||||
|
types={{ 'myvalue': 'number' }} |
||||||
|
value={{ '': 123 }} |
||||||
|
onChange={value => { |
||||||
|
expect(value).to.have.property('myvalue', 1234); |
||||||
|
done(); |
||||||
|
}} |
||||||
|
/>, document.body); |
||||||
|
|
||||||
|
let input = document.querySelector('input[name=myvalue]'); |
||||||
|
input.value = '1234' |
||||||
|
input.dispatchEvent(new Event('change')) |
||||||
|
}); |
||||||
|
|
||||||
|
it('invokes onChange event on checkbox changed', (done) => { |
||||||
|
render(<PropertiesForm |
||||||
|
types={{ 'myvalue': 'boolean' }} |
||||||
|
value={{ 'myvalue': false }} |
||||||
|
onChange={value => { |
||||||
|
expect(value).to.have.property('myvalue', true); |
||||||
|
done(); |
||||||
|
}} |
||||||
|
/>, document.body); |
||||||
|
|
||||||
|
let input = document.querySelector('input[name=myvalue]'); |
||||||
|
input.click(); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,85 @@ |
|||||||
|
import { expect } from "chai"; |
||||||
|
import * as parsers from 'shared/commands/parsers'; |
||||||
|
|
||||||
|
describe("shared/commands/parsers", () => { |
||||||
|
describe("#parsers.parseSetOption", () => { |
||||||
|
it('parse set string', () => { |
||||||
|
let [key, value] = parsers.parseSetOption('encoding=utf-8', { encoding: 'string' }); |
||||||
|
expect(key).to.equal('encoding'); |
||||||
|
expect(value).to.equal('utf-8'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('parse set empty string', () => { |
||||||
|
let [key, value] = parsers.parseSetOption('encoding=', { encoding: 'string' }); |
||||||
|
expect(key).to.equal('encoding'); |
||||||
|
expect(value).to.equal(''); |
||||||
|
}); |
||||||
|
|
||||||
|
it('parse set string', () => { |
||||||
|
let [key, value] = parsers.parseSetOption('history=50', { history: 'number' }); |
||||||
|
expect(key).to.equal('history'); |
||||||
|
expect(value).to.equal(50); |
||||||
|
}); |
||||||
|
|
||||||
|
it('parse set boolean', () => { |
||||||
|
let [key, value] = parsers.parseSetOption('paste', { paste: 'boolean' }); |
||||||
|
expect(key).to.equal('paste'); |
||||||
|
expect(value).to.be.true; |
||||||
|
|
||||||
|
[key, value] = parsers.parseSetOption('nopaste', { paste: 'boolean' }); |
||||||
|
expect(key).to.equal('paste'); |
||||||
|
expect(value).to.be.false; |
||||||
|
}); |
||||||
|
|
||||||
|
it('throws error on unknown property', () => { |
||||||
|
expect(() => parsers.parseSetOption('charset=utf-8', {})).to.throw(Error, 'Unknown'); |
||||||
|
expect(() => parsers.parseSetOption('smoothscroll', {})).to.throw(Error, 'Unknown'); |
||||||
|
expect(() => parsers.parseSetOption('nosmoothscroll', {})).to.throw(Error, 'Unknown'); |
||||||
|
}) |
||||||
|
|
||||||
|
it('throws error on invalid property', () => { |
||||||
|
expect(() => parsers.parseSetOption('charset=utf-8', { charset: 'number' })).to.throw(Error, 'Not number'); |
||||||
|
expect(() => parsers.parseSetOption('charset=utf-8', { charset: 'boolean' })).to.throw(Error, 'Invalid'); |
||||||
|
expect(() => parsers.parseSetOption('charset=', { charset: 'boolean' })).to.throw(Error, 'Invalid'); |
||||||
|
expect(() => parsers.parseSetOption('smoothscroll', { smoothscroll: 'string' })).to.throw(Error, 'Invalid'); |
||||||
|
expect(() => parsers.parseSetOption('smoothscroll', { smoothscroll: 'number' })).to.throw(Error, 'Invalid'); |
||||||
|
}) |
||||||
|
}); |
||||||
|
|
||||||
|
describe('#normalizeUrl', () => { |
||||||
|
const config = { |
||||||
|
default: 'google', |
||||||
|
engines: { |
||||||
|
google: 'https://google.com/search?q={}', |
||||||
|
yahoo: 'https://yahoo.com/search?q={}', |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
it('convertes search url', () => { |
||||||
|
expect(parsers.normalizeUrl(['google', 'apple'], config)) |
||||||
|
.to.equal('https://google.com/search?q=apple'); |
||||||
|
expect(parsers.normalizeUrl(['yahoo', 'apple'], config)) |
||||||
|
.to.equal('https://yahoo.com/search?q=apple'); |
||||||
|
expect(parsers.normalizeUrl(['google', 'apple', 'banana'], config)) |
||||||
|
.to.equal('https://google.com/search?q=apple%20banana'); |
||||||
|
expect(parsers.normalizeUrl(['yahoo', 'C++CLI'], config)) |
||||||
|
.to.equal('https://yahoo.com/search?q=C%2B%2BCLI'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('user default search engine', () => { |
||||||
|
expect(parsers.normalizeUrl(['apple', 'banana'], config)) |
||||||
|
.to.equal('https://google.com/search?q=apple%20banana'); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('#parseCommandLine', () => { |
||||||
|
it('parse command line as name and args', () => { |
||||||
|
expect(parsers.parseCommandLine('open google apple')).to.deep.equal(['open', ['google', 'apple']]); |
||||||
|
expect(parsers.parseCommandLine(' open google apple ')).to.deep.equal(['open', ['google', 'apple']]); |
||||||
|
expect(parsers.parseCommandLine('')).to.deep.equal(['', []]); |
||||||
|
expect(parsers.parseCommandLine(' ')).to.deep.equal(['', []]); |
||||||
|
expect(parsers.parseCommandLine('exit')).to.deep.equal(['exit', []]); |
||||||
|
expect(parsers.parseCommandLine(' exit ')).to.deep.equal(['exit', []]); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
@ -1,5 +1,5 @@ |
|||||||
import { expect } from "chai"; |
import { expect } from "chai"; |
||||||
import { validate } from 'shared/validators/setting'; |
import { validate } from 'shared/settings/validator'; |
||||||
|
|
||||||
describe("setting validator", () => { |
describe("setting validator", () => { |
||||||
describe("unknown top keys", () => { |
describe("unknown top keys", () => { |
Reference in new issue