commit
0b37c2250e
30 changed files with 1189 additions and 146 deletions
@ -0,0 +1,52 @@ |
||||
import './blacklist-form.scss'; |
||||
import AddButton from '../ui/add-button'; |
||||
import DeleteButton from '../ui/delete-button'; |
||||
import { h, Component } from 'preact'; |
||||
|
||||
class BlacklistForm extends Component { |
||||
|
||||
render() { |
||||
let value = this.props.value; |
||||
if (!value) { |
||||
value = []; |
||||
} |
||||
|
||||
return <div className='form-blacklist-form'> |
||||
{ |
||||
value.map((url, index) => { |
||||
return <div key={index} className='form-blacklist-form-row'> |
||||
<input data-index={index} type='text' name='url' |
||||
className='column-url' value={url} |
||||
onChange={this.bindValue.bind(this)} /> |
||||
<DeleteButton data-index={index} name='delete' |
||||
onClick={this.bindValue.bind(this)} /> |
||||
</div>; |
||||
}) |
||||
} |
||||
<AddButton name='add' style='float:right' |
||||
onClick={this.bindValue.bind(this)} /> |
||||
</div>; |
||||
} |
||||
|
||||
bindValue(e) { |
||||
if (!this.props.onChange) { |
||||
return; |
||||
} |
||||
|
||||
let name = e.target.name; |
||||
let index = e.target.getAttribute('data-index'); |
||||
let next = this.props.value ? this.props.value.slice() : []; |
||||
|
||||
if (name === 'url') { |
||||
next[index] = e.target.value; |
||||
} else if (name === 'add') { |
||||
next.push(''); |
||||
} else if (name === 'delete') { |
||||
next.splice(index, 1); |
||||
} |
||||
|
||||
this.props.onChange(next); |
||||
} |
||||
} |
||||
|
||||
export default BlacklistForm; |
@ -0,0 +1,9 @@ |
||||
.form-blacklist-form { |
||||
&-row { |
||||
display: flex; |
||||
|
||||
.column-url { |
||||
flex: 1; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,99 @@ |
||||
import './keymaps-form.scss'; |
||||
import { h, Component } from 'preact'; |
||||
import Input from '../ui/input'; |
||||
|
||||
const KeyMapFields = [ |
||||
[ |
||||
['scroll.vertically?{"count":1}', 'Scroll down'], |
||||
['scroll.vertically?{"count":-1}', 'Scroll up'], |
||||
['scroll.horizonally?{"count":-1}', 'Scroll left'], |
||||
['scroll.horizonally?{"count":1}', 'Scroll right'], |
||||
['scroll.home', 'Scroll leftmost'], |
||||
['scroll.end', 'Scroll last'], |
||||
['scroll.pages?{"count":-0.5}', 'Scroll up by half of screen'], |
||||
['scroll.pages?{"count":0.5}', 'Scroll up by half of screen'], |
||||
['scroll.pages?{"count":-1}', 'Scroll up by a screen'], |
||||
['scroll.pages?{"count":1}', 'Scroll up by a screen'], |
||||
], [ |
||||
['tabs.close', 'Close a tab'], |
||||
['tabs.reopen', 'Reopen closed tab'], |
||||
['tabs.next?{"count":1}', 'Select next Tab'], |
||||
['tabs.prev?{"count":1}', 'Select prev Tab'], |
||||
['tabs.first', 'Select first tab'], |
||||
['tabs.last', 'Select last tab'], |
||||
['tabs.reload?{"cache":true}', 'Reload current tab'], |
||||
['tabs.pin.toggle', 'Toggle pinned state'], |
||||
['tabs.duplicate', 'Dupplicate a tab'], |
||||
], [ |
||||
['follow.start?{"newTab":false}', 'Follow a link'], |
||||
['follow.start?{"newTab":true}', 'Follow a link in new tab'], |
||||
['navigate.history.prev', 'Go back in histories'], |
||||
['navigate.history.next', 'Go forward in histories'], |
||||
['navigate.link.next', 'Open next link'], |
||||
['navigate.link.prev', 'Open previous link'], |
||||
['navigate.parent', 'Go to parent directory'], |
||||
['navigate.root', 'Go to root directory'], |
||||
], [ |
||||
['find.start', 'Start find mode'], |
||||
['find.next', 'Find next word'], |
||||
['find.prev', 'Find previous word'], |
||||
], [ |
||||
['command.show', 'Open console'], |
||||
['command.show.open?{"alter":false}', 'Open URL'], |
||||
['command.show.open?{"alter":true}', 'Alter URL'], |
||||
['command.show.tabopen?{"alter":false}', 'Open URL in new Tab'], |
||||
['command.show.tabopen?{"alter":true}', 'Alter URL in new Tab'], |
||||
['command.show.winopen?{"alter":false}', 'Open URL in new window'], |
||||
['command.show.winopen?{"alter":true}', 'Alter URL in new window'], |
||||
['command.show.buffer', 'Open buffer command'], |
||||
], [ |
||||
['addon.toggle.enabled', 'Enable or disable'], |
||||
['urls.yank', 'Copy current URL'], |
||||
['zoom.in', 'Zoom-in'], |
||||
['zoom.out', 'Zoom-out'], |
||||
['zoom.neutral', 'Reset zoom level'], |
||||
] |
||||
]; |
||||
|
||||
class KeymapsForm extends Component { |
||||
|
||||
render() { |
||||
let values = this.props.value; |
||||
if (!values) { |
||||
values = {}; |
||||
} |
||||
return <div className='keymap-fields'> |
||||
{ |
||||
KeyMapFields.map((group, index) => { |
||||
return <div key={index} className='form-keymaps-form'> |
||||
{ |
||||
group.map((field) => { |
||||
let name = field[0]; |
||||
let label = field[1]; |
||||
let value = values[name]; |
||||
return <Input |
||||
type='text' id={name} name={name} key={name} |
||||
label={label} value={value} |
||||
onChange={this.bindValue.bind(this)} |
||||
/>; |
||||
}) |
||||
} |
||||
</div>; |
||||
}) |
||||
} |
||||
</div>; |
||||
} |
||||
|
||||
bindValue(e) { |
||||
if (!this.props.onChange) { |
||||
return; |
||||
} |
||||
|
||||
let next = Object.assign({}, this.props.value); |
||||
next[e.target.name] = e.target.value; |
||||
|
||||
this.props.onChange(next); |
||||
} |
||||
} |
||||
|
||||
export default KeymapsForm; |
@ -0,0 +1,9 @@ |
||||
.form-keymaps-form { |
||||
column-count: 3; |
||||
.keymap-fields-group { |
||||
margin-top: 24px; |
||||
} |
||||
.keymap-fields-group:first-of-type { |
||||
margin-top: 0; |
||||
} |
||||
} |
@ -0,0 +1,78 @@ |
||||
import './search-form.scss'; |
||||
import { h, Component } from 'preact'; |
||||
import AddButton from '../ui/add-button'; |
||||
import DeleteButton from '../ui/delete-button'; |
||||
|
||||
class SearchForm extends Component { |
||||
|
||||
render() { |
||||
let value = this.props.value; |
||||
if (!value) { |
||||
value = { default: '', engines: []}; |
||||
} |
||||
if (!value.engines) { |
||||
value.engines = []; |
||||
} |
||||
|
||||
return <div className='form-search-form'> |
||||
<div className='form-search-form-header'> |
||||
<div className='column-name'>Name</div> |
||||
<div className='column-url'>URL</div> |
||||
<div className='column-option'>Default</div> |
||||
</div> |
||||
{ |
||||
value.engines.map((engine, index) => { |
||||
return <div key={index} className='form-search-form-row'> |
||||
<input data-index={index} type='text' name='name' |
||||
className='column-name' value={engine[0]} |
||||
onChange={this.bindValue.bind(this)} /> |
||||
<input data-index={index} type='text' name='url' |
||||
placeholder='http://example.com/?q={}' |
||||
className='column-url' value={engine[1]} |
||||
onChange={this.bindValue.bind(this)} /> |
||||
<div className='column-option'> |
||||
<input data-index={index} type='radio' name='default' |
||||
checked={value.default === engine[0]} |
||||
onChange={this.bindValue.bind(this)} /> |
||||
<DeleteButton data-index={index} name='delete' |
||||
onClick={this.bindValue.bind(this)} /> |
||||
</div> |
||||
</div>; |
||||
}) |
||||
} |
||||
<AddButton name='add' style='float:right' |
||||
onClick={this.bindValue.bind(this)} /> |
||||
</div>; |
||||
} |
||||
|
||||
bindValue(e) { |
||||
if (!this.props.onChange) { |
||||
return; |
||||
} |
||||
|
||||
let value = this.props.value; |
||||
let name = e.target.name; |
||||
let index = e.target.getAttribute('data-index'); |
||||
let next = Object.assign({}, { |
||||
default: value.default, |
||||
engines: value.engines ? value.engines.slice() : [], |
||||
}); |
||||
|
||||
if (name === 'name') { |
||||
next.engines[index][0] = e.target.value; |
||||
next.default = this.props.value.engines[index][0]; |
||||
} else if (name === 'url') { |
||||
next.engines[index][1] = e.target.value; |
||||
} else if (name === 'default') { |
||||
next.default = this.props.value.engines[index][0]; |
||||
} else if (name === 'add') { |
||||
next.engines.push(['', '']); |
||||
} else if (name === 'delete') { |
||||
next.engines.splice(index, 1); |
||||
} |
||||
|
||||
this.props.onChange(next); |
||||
} |
||||
} |
||||
|
||||
export default SearchForm; |
@ -0,0 +1,28 @@ |
||||
.form-search-form { |
||||
@mixin row-base { |
||||
display: flex; |
||||
|
||||
.column-name { |
||||
flex: 1; |
||||
min-width: 0; |
||||
} |
||||
.column-url { |
||||
flex: 5; |
||||
min-width: 0; |
||||
} |
||||
.column-option { |
||||
text-align: right; |
||||
flex-basis: 5rem; |
||||
} |
||||
} |
||||
|
||||
&-header { |
||||
@include row-base; |
||||
|
||||
font-weight: bold; |
||||
} |
||||
|
||||
&-row { |
||||
@include row-base; |
||||
} |
||||
} |
@ -1,8 +1,27 @@ |
||||
.vimvixen-settings-form { |
||||
padding: 2px; |
||||
|
||||
textarea[name=json] { |
||||
font-family: monospace; |
||||
width: 100%; |
||||
min-height: 64ex; |
||||
resize: vertical; |
||||
} |
||||
|
||||
fieldset { |
||||
margin: 0; |
||||
padding: 0; |
||||
border: none; |
||||
margin-top: 1rem; |
||||
|
||||
fieldset:first-of-type { |
||||
margin-top: 1rem; |
||||
} |
||||
|
||||
legend { |
||||
font-size: 1.5rem; |
||||
padding: .5rem 0; |
||||
font-weight: bold; |
||||
} |
||||
} |
||||
} |
||||
|
@ -0,0 +1,12 @@ |
||||
import './add-button.scss'; |
||||
import { h, Component } from 'preact'; |
||||
|
||||
class AddButton extends Component { |
||||
render() { |
||||
return <input |
||||
className='ui-add-button' type='button' value='✚' |
||||
{...this.props} />; |
||||
} |
||||
} |
||||
|
||||
export default AddButton; |
@ -0,0 +1,13 @@ |
||||
.ui-add-button { |
||||
border: none; |
||||
padding: 4; |
||||
display: inline; |
||||
background: none; |
||||
font-weight: bold; |
||||
color: green; |
||||
cursor: pointer; |
||||
|
||||
&:hover { |
||||
color: darkgreen; |
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
import './delete-button.scss'; |
||||
import { h, Component } from 'preact'; |
||||
|
||||
class DeleteButton extends Component { |
||||
render() { |
||||
return <input |
||||
className='ui-delete-button' type='button' value='✖' |
||||
{...this.props} />; |
||||
} |
||||
} |
||||
|
||||
export default DeleteButton; |
@ -0,0 +1,13 @@ |
||||
|
||||
.ui-delete-button { |
||||
border: none; |
||||
padding: 4; |
||||
display: inline; |
||||
background: none; |
||||
color: red; |
||||
cursor: pointer; |
||||
|
||||
&:hover { |
||||
color: darkred; |
||||
} |
||||
} |
@ -0,0 +1,52 @@ |
||||
import { h, Component } from 'preact'; |
||||
import './input.scss'; |
||||
|
||||
class Input extends Component { |
||||
|
||||
renderText(props) { |
||||
let inputClassName = props.error ? 'input-error' : ''; |
||||
return <div className='settings-ui-input'> |
||||
<label htmlFor={props.id}>{ props.label }</label> |
||||
<input type='text' className={inputClassName} {...props} /> |
||||
</div>; |
||||
} |
||||
|
||||
renderRadio(props) { |
||||
let inputClassName = props.error ? 'input-error' : ''; |
||||
return <div className='settings-ui-input'> |
||||
<label> |
||||
<input type='radio' className={inputClassName} {...props} /> |
||||
{ props.label } |
||||
</label> |
||||
</div>; |
||||
} |
||||
|
||||
renderTextArea(props) { |
||||
let inputClassName = props.error ? 'input-error' : ''; |
||||
return <div className='settings-ui-input'> |
||||
<label |
||||
htmlFor={props.id} |
||||
>{ props.label }</label> |
||||
<textarea className={inputClassName} {...props} /> |
||||
<p className='settings-ui-input-error'>{ this.props.error }</p> |
||||
</div>; |
||||
} |
||||
|
||||
render() { |
||||
let { type } = this.props; |
||||
|
||||
switch (this.props.type) { |
||||
case 'text': |
||||
return this.renderText(this.props); |
||||
case 'radio': |
||||
return this.renderRadio(this.props); |
||||
case 'textarea': |
||||
return this.renderTextArea(this.props); |
||||
default: |
||||
console.warn(`Unsupported input type ${type}`); |
||||
} |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
export default Input; |
@ -0,0 +1,29 @@ |
||||
.settings-ui-input { |
||||
page-break-inside: avoid; |
||||
|
||||
* { |
||||
page-break-inside: avoid; |
||||
} |
||||
|
||||
label { |
||||
font-weight: bold; |
||||
min-width: 14rem; |
||||
display: inline-block; |
||||
} |
||||
|
||||
input[type='text'] { |
||||
padding: 4px; |
||||
width: 8rem; |
||||
} |
||||
|
||||
input.input-crror, |
||||
textarea.input-error { |
||||
box-shadow: 0 0 2px red; |
||||
} |
||||
|
||||
&-error { |
||||
font-weight: bold; |
||||
color: red; |
||||
min-height: 1.5em; |
||||
} |
||||
} |
@ -0,0 +1,100 @@ |
||||
import DefaultSettings from './default'; |
||||
|
||||
const operationFromFormName = (name) => { |
||||
let [type, argStr] = name.split('?'); |
||||
let args = {}; |
||||
if (argStr) { |
||||
args = JSON.parse(argStr); |
||||
} |
||||
return Object.assign({ type }, args); |
||||
}; |
||||
|
||||
const operationToFormName = (op) => { |
||||
let type = op.type; |
||||
let args = Object.assign({}, op); |
||||
delete args.type; |
||||
|
||||
if (Object.keys(args).length === 0) { |
||||
return type; |
||||
} |
||||
return op.type + '?' + JSON.stringify(args); |
||||
}; |
||||
|
||||
const valueFromJson = (json) => { |
||||
return JSON.parse(json); |
||||
}; |
||||
|
||||
const valueFromForm = (form) => { |
||||
let keymaps = undefined; |
||||
if (form.keymaps) { |
||||
keymaps = {}; |
||||
for (let name of Object.keys(form.keymaps)) { |
||||
let keys = form.keymaps[name]; |
||||
keymaps[keys] = operationFromFormName(name); |
||||
} |
||||
} |
||||
|
||||
let search = undefined; |
||||
if (form.search) { |
||||
search = { default: form.search.default }; |
||||
|
||||
if (form.search.engines) { |
||||
search.engines = {}; |
||||
for (let [name, url] of form.search.engines) { |
||||
search.engines[name] = url; |
||||
} |
||||
} |
||||
} |
||||
|
||||
let blacklist = form.blacklist; |
||||
|
||||
return { keymaps, search, blacklist }; |
||||
}; |
||||
|
||||
const jsonFromValue = (value) => { |
||||
return JSON.stringify(value, undefined, 2); |
||||
}; |
||||
|
||||
const formFromValue = (value) => { |
||||
|
||||
let keymaps = undefined; |
||||
if (value.keymaps) { |
||||
let allowedOps = new Set(Object.keys(DefaultSettings.form.keymaps)); |
||||
|
||||
keymaps = {}; |
||||
for (let keys of Object.keys(value.keymaps)) { |
||||
let op = operationToFormName(value.keymaps[keys]); |
||||
if (allowedOps.has(op)) { |
||||
keymaps[op] = keys; |
||||
} |
||||
} |
||||
} |
||||
|
||||
let search = undefined; |
||||
if (value.search) { |
||||
search = { default: value.search.default }; |
||||
if (value.search.engines) { |
||||
search.engines = Object.keys(value.search.engines).map((name) => { |
||||
return [name, value.search.engines[name]]; |
||||
}); |
||||
} |
||||
} |
||||
|
||||
let blacklist = value.blacklist; |
||||
|
||||
return { keymaps, search, blacklist }; |
||||
}; |
||||
|
||||
const jsonFromForm = (form) => { |
||||
return jsonFromValue(valueFromForm(form)); |
||||
}; |
||||
|
||||
const formFromJson = (json) => { |
||||
let value = valueFromJson(json); |
||||
return formFromValue(value); |
||||
}; |
||||
|
||||
export { |
||||
valueFromJson, valueFromForm, jsonFromValue, formFromValue, |
||||
jsonFromForm, formFromJson |
||||
}; |
@ -1,18 +1,15 @@ |
||||
import React from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import { h, Component } from 'preact'; |
||||
|
||||
class Provider extends React.PureComponent { |
||||
class Provider extends Component { |
||||
getChildContext() { |
||||
return { store: this.props.store }; |
||||
} |
||||
|
||||
render() { |
||||
return React.Children.only(this.props.children); |
||||
return <div> |
||||
{ this.props.children } |
||||
</div>; |
||||
} |
||||
} |
||||
|
||||
Provider.childContextTypes = { |
||||
store: PropTypes.any, |
||||
}; |
||||
|
||||
export default Provider; |
||||
|
@ -0,0 +1,82 @@ |
||||
import { expect } from 'chai'; |
||||
import { h, render } from 'preact'; |
||||
import BlacklistForm from 'settings/components/form/blacklist-form' |
||||
|
||||
describe("settings/form/BlacklistForm", () => { |
||||
beforeEach(() => { |
||||
document.body.innerHTML = ''; |
||||
}); |
||||
|
||||
describe('render', () => { |
||||
it('renders BlacklistForm', () => { |
||||
render(<BlacklistForm value={['*.slack.com', 'www.google.com/maps']} />, document.body); |
||||
|
||||
let inputs = document.querySelectorAll('input[type=text]'); |
||||
expect(inputs).to.have.lengthOf(2); |
||||
expect(inputs[0].value).to.equal('*.slack.com'); |
||||
expect(inputs[1].value).to.equal('www.google.com/maps'); |
||||
}); |
||||
|
||||
it('renders blank value', () => { |
||||
render(<BlacklistForm />, document.body); |
||||
|
||||
let inputs = document.querySelectorAll('input[type=text]'); |
||||
expect(inputs).to.be.empty; |
||||
}); |
||||
|
||||
it('renders blank value', () => { |
||||
render(<BlacklistForm />, document.body); |
||||
|
||||
let inputs = document.querySelectorAll('input[type=text]'); |
||||
expect(inputs).to.be.empty; |
||||
}); |
||||
}); |
||||
|
||||
describe('onChange', () => { |
||||
it('invokes onChange event on edit', (done) => { |
||||
render(<BlacklistForm |
||||
value={['*.slack.com', 'www.google.com/maps*']} |
||||
onChange={value => { |
||||
expect(value).to.have.lengthOf(2) |
||||
.and.have.members(['gitter.im', 'www.google.com/maps*']); |
||||
|
||||
done(); |
||||
}} |
||||
/>, document.body); |
||||
|
||||
let input = document.querySelectorAll('input[type=text]')[0]; |
||||
input.value = 'gitter.im'; |
||||
input.dispatchEvent(new Event('change')) |
||||
}); |
||||
|
||||
it('invokes onChange event on delete', (done) => { |
||||
render(<BlacklistForm |
||||
value={['*.slack.com', 'www.google.com/maps*']} |
||||
onChange={value => { |
||||
expect(value).to.have.lengthOf(1) |
||||
.and.have.members(['www.google.com/maps*']); |
||||
|
||||
done(); |
||||
}} |
||||
/>, document.body); |
||||
|
||||
let button = document.querySelectorAll('input[type=button]')[0]; |
||||
button.click(); |
||||
}); |
||||
|
||||
it('invokes onChange event on add', (done) => { |
||||
render(<BlacklistForm |
||||
value={['*.slack.com']} |
||||
onChange={value => { |
||||
expect(value).to.have.lengthOf(2) |
||||
.and.have.members(['*.slack.com', '']); |
||||
|
||||
done(); |
||||
}} |
||||
/>, document.body); |
||||
|
||||
let button = document.querySelector('input[type=button].ui-add-button'); |
||||
button.click(); |
||||
}); |
||||
}); |
||||
}); |
@ -0,0 +1,53 @@ |
||||
import { expect } from 'chai'; |
||||
import { h, render } from 'preact'; |
||||
import KeymapsForm from 'settings/components/form/keymaps-form' |
||||
|
||||
describe("settings/form/KeymapsForm", () => { |
||||
beforeEach(() => { |
||||
document.body.innerHTML = ''; |
||||
}); |
||||
|
||||
describe('render', () => { |
||||
it('renders KeymapsForm', () => { |
||||
render(<KeymapsForm value={{ |
||||
'scroll.vertically?{"count":1}': 'j', |
||||
'scroll.vertically?{"count":-1}': 'k', |
||||
}} />, document.body); |
||||
|
||||
let inputj = document.getElementById('scroll.vertically?{"count":1}'); |
||||
let inputk = document.getElementById('scroll.vertically?{"count":-1}'); |
||||
|
||||
expect(inputj.value).to.equal('j'); |
||||
expect(inputk.value).to.equal('k'); |
||||
}); |
||||
|
||||
it('renders blank value', () => { |
||||
render(<KeymapsForm />, document.body); |
||||
|
||||
let inputj = document.getElementById('scroll.vertically?{"count":1}'); |
||||
let inputk = document.getElementById('scroll.vertically?{"count":-1}'); |
||||
|
||||
expect(inputj.value).to.be.empty; |
||||
expect(inputk.value).to.be.empty; |
||||
}); |
||||
}); |
||||
|
||||
describe('onChange event', () => { |
||||
it('invokes onChange event on edit', (done) => { |
||||
render(<KeymapsForm |
||||
value={{ |
||||
'scroll.vertically?{"count":1}': 'j', |
||||
'scroll.vertically?{"count":-1}': 'k', |
||||
}} |
||||
onChange={value => { |
||||
expect(value['scroll.vertically?{"count":1}']).to.equal('jjj'); |
||||
|
||||
done(); |
||||
}} />, document.body); |
||||
|
||||
let input = document.getElementById('scroll.vertically?{"count":1}'); |
||||
input.value = 'jjj'; |
||||
input.dispatchEvent(new Event('change')) |
||||
}); |
||||
}); |
||||
}); |
@ -0,0 +1,104 @@ |
||||
import { expect } from 'chai'; |
||||
import { h, render } from 'preact'; |
||||
import SearchForm from 'settings/components/form/search-form' |
||||
|
||||
describe("settings/form/SearchForm", () => { |
||||
beforeEach(() => { |
||||
document.body.innerHTML = ''; |
||||
}); |
||||
|
||||
describe('render', () => { |
||||
it('renders SearchForm', () => { |
||||
render(<SearchForm value={{ |
||||
default: 'google', |
||||
engines: [['google', 'google.com'], ['yahoo', 'yahoo.com']], |
||||
}} />, document.body); |
||||
|
||||
let names = document.querySelectorAll('input[name=name]'); |
||||
expect(names).to.have.lengthOf(2); |
||||
expect(names[0].value).to.equal('google'); |
||||
expect(names[1].value).to.equal('yahoo'); |
||||
|
||||
let urls = document.querySelectorAll('input[name=url]'); |
||||
expect(urls).to.have.lengthOf(2); |
||||
expect(urls[0].value).to.equal('google.com'); |
||||
expect(urls[1].value).to.equal('yahoo.com'); |
||||
}); |
||||
|
||||
it('renders blank value', () => { |
||||
render(<SearchForm />, document.body); |
||||
|
||||
let names = document.querySelectorAll('input[name=name]'); |
||||
let urls = document.querySelectorAll('input[name=url]'); |
||||
expect(names).to.have.lengthOf(0); |
||||
expect(urls).to.have.lengthOf(0); |
||||
}); |
||||
|
||||
it('renders blank engines', () => { |
||||
render(<SearchForm value={{ default: 'google' }} />, document.body); |
||||
|
||||
let names = document.querySelectorAll('input[name=name]'); |
||||
let urls = document.querySelectorAll('input[name=url]'); |
||||
expect(names).to.have.lengthOf(0); |
||||
expect(urls).to.have.lengthOf(0); |
||||
}); |
||||
}); |
||||
|
||||
describe('onChange event', () => { |
||||
it('invokes onChange event on edit', (done) => { |
||||
render(<SearchForm |
||||
value={{ |
||||
default: 'google', |
||||
engines: [['google', 'google.com'], ['yahoo', 'yahoo.com']] |
||||
}} |
||||
onChange={value => { |
||||
expect(value.default).to.equal('louvre'); |
||||
expect(value.engines).to.have.lengthOf(2) |
||||
.and.have.deep.members([['louvre', 'google.com'], ['yahoo', 'yahoo.com']]) |
||||
|
||||
done(); |
||||
}} />, document.body); |
||||
|
||||
let radio = document.querySelectorAll('input[type=radio]'); |
||||
radio.checked = true; |
||||
|
||||
let name = document.querySelector('input[name=name]'); |
||||
name.value = 'louvre'; |
||||
name.dispatchEvent(new Event('change')) |
||||
}); |
||||
|
||||
it('invokes onChange event on delete', (done) => { |
||||
render(<SearchForm value={{ |
||||
default: 'yahoo', |
||||
engines: [['louvre', 'google.com'], ['yahoo', 'yahoo.com']] |
||||
}} |
||||
onChange={value => { |
||||
expect(value.default).to.equal('yahoo'); |
||||
expect(value.engines).to.have.lengthOf(1) |
||||
.and.have.deep.members([['yahoo', 'yahoo.com']]) |
||||
|
||||
done(); |
||||
}} />, document.body); |
||||
|
||||
let button = document.querySelector('input[type=button]'); |
||||
button.click(); |
||||
}); |
||||
|
||||
it('invokes onChange event on add', (done) => { |
||||
render(<SearchForm value={{ |
||||
default: 'yahoo', |
||||
engines: [['google', 'google.com']] |
||||
}} |
||||
onChange={value => { |
||||
expect(value.default).to.equal('yahoo'); |
||||
expect(value.engines).to.have.lengthOf(2) |
||||
.and.have.deep.members([['google', 'google.com'], ['', '']]) |
||||
|
||||
done(); |
||||
}} />, document.body); |
||||
|
||||
let button = document.querySelector('input[type=button].ui-add-button'); |
||||
button.click(); |
||||
}); |
||||
}); |
||||
}); |
@ -0,0 +1,83 @@ |
||||
import { expect } from 'chai'; |
||||
import { h, render } from 'preact'; |
||||
import Input from 'settings/components/ui/input' |
||||
|
||||
describe("settings/ui/Input", () => { |
||||
beforeEach(() => { |
||||
document.body.innerHTML = ''; |
||||
}); |
||||
|
||||
context("type=text", () => { |
||||
it('renders text input', () => { |
||||
render(<Input type='text' name='myname' label='myfield' value='myvalue'/>, document.body) |
||||
|
||||
let label = document.querySelector('label'); |
||||
let input = document.querySelector('input'); |
||||
expect(label.textContent).to.contain('myfield'); |
||||
expect(input.type).to.contain('text'); |
||||
expect(input.name).to.contain('myname'); |
||||
expect(input.value).to.contain('myvalue'); |
||||
}); |
||||
|
||||
it('invoke onChange', (done) => { |
||||
render(<Input type='text' name='myname' label='myfield' value='myvalue' onChange={(e) => { |
||||
expect(e.target.value).to.equal('newvalue'); |
||||
done(); |
||||
}}/>, document.body); |
||||
|
||||
let input = document.querySelector('input'); |
||||
input.value = 'newvalue'; |
||||
input.dispatchEvent(new Event('change')) |
||||
}); |
||||
}); |
||||
|
||||
context("type=radio", () => { |
||||
it('renders radio button', () => { |
||||
render(<Input type='radio' name='myname' label='myfield' value='myvalue'/>, document.body) |
||||
|
||||
let label = document.querySelector('label'); |
||||
let input = document.querySelector('input'); |
||||
expect(label.textContent).to.contain('myfield'); |
||||
expect(input.type).to.contain('radio'); |
||||
expect(input.name).to.contain('myname'); |
||||
expect(input.value).to.contain('myvalue'); |
||||
}); |
||||
|
||||
it('invoke onChange', (done) => { |
||||
render(<Input type='text' name='radio' label='myfield' value='myvalue' onChange={(e) => { |
||||
expect(e.target.checked).to.be.true; |
||||
done(); |
||||
}}/>, document.body); |
||||
|
||||
let input = document.querySelector('input'); |
||||
input.checked = true; |
||||
input.dispatchEvent(new Event('change')) |
||||
}); |
||||
}); |
||||
|
||||
context("type=textarea", () => { |
||||
it('renders textarea button', () => { |
||||
render(<Input type='textarea' name='myname' label='myfield' value='myvalue' error='myerror' />, document.body) |
||||
|
||||
let label = document.querySelector('label'); |
||||
let textarea = document.querySelector('textarea'); |
||||
let error = document.querySelector('.settings-ui-input-error'); |
||||
expect(label.textContent).to.contain('myfield'); |
||||
expect(textarea.nodeName).to.contain('TEXTAREA'); |
||||
expect(textarea.name).to.contain('myname'); |
||||
expect(textarea.value).to.contain('myvalue'); |
||||
expect(error.textContent).to.contain('myerror'); |
||||
}); |
||||
|
||||
it('invoke onChange', (done) => { |
||||
render(<Input type='textarea' name='myname' label='myfield' value='myvalue' onChange={(e) => { |
||||
expect(e.target.value).to.equal('newvalue'); |
||||
done(); |
||||
}}/>, document.body); |
||||
|
||||
let input = document.querySelector('textarea'); |
||||
input.value = 'newvalue' |
||||
input.dispatchEvent(new Event('change')) |
||||
}); |
||||
}); |
||||
}); |
@ -0,0 +1,112 @@ |
||||
import { expect } from 'chai'; |
||||
import * as values from 'shared/settings/values'; |
||||
|
||||
describe("settings values", () => { |
||||
describe('valueFromJson', () => { |
||||
it('return object from json string', () => { |
||||
let json = `{
|
||||
"keymaps": { "0": {"type": "scroll.home"}}, |
||||
"search": { "default": "google", "engines": { "google": "https://google.com/search?q={}" }}, |
||||
"blacklist": [ "*.slack.com"] |
||||
}`;
|
||||
let value = values.valueFromJson(json); |
||||
|
||||
expect(value.keymaps).to.deep.equal({ 0: {type: "scroll.home"}}); |
||||
expect(value.search).to.deep.equal({ default: "google", engines: { google: "https://google.com/search?q={}"} }); |
||||
expect(value.blacklist).to.deep.equal(["*.slack.com"]); |
||||
}); |
||||
}); |
||||
|
||||
describe('valueFromForm', () => { |
||||
it('returns value from form', () => { |
||||
let form = { |
||||
keymaps: { |
||||
'scroll.vertically?{"count":1}': 'j', |
||||
'scroll.home': '0', |
||||
}, |
||||
search: { |
||||
default: 'google', |
||||
engines: [['google', 'https://google.com/search?q={}']], |
||||
}, |
||||
blacklist: ['*.slack.com'], |
||||
}; |
||||
let value = values.valueFromForm(form); |
||||
|
||||
expect(value.keymaps).to.have.deep.property('j', { type: "scroll.vertically", count: 1 }); |
||||
expect(value.keymaps).to.have.deep.property('0', { type: "scroll.home" }); |
||||
expect(JSON.stringify(value.search)).to.deep.equal(JSON.stringify({ default: "google", engines: { google: "https://google.com/search?q={}"} })); |
||||
expect(value.search).to.deep.equal({ default: "google", engines: { google: "https://google.com/search?q={}"} }); |
||||
expect(value.blacklist).to.deep.equal(["*.slack.com"]); |
||||
}); |
||||
|
||||
it('convert from empty form', () => { |
||||
let form = {}; |
||||
let value = values.valueFromForm(form); |
||||
expect(value).to.not.have.key('keymaps'); |
||||
expect(value).to.not.have.key('search'); |
||||
expect(value).to.not.have.key('blacklist'); |
||||
}); |
||||
|
||||
it('override keymaps', () => { |
||||
let form = { |
||||
keymaps: { |
||||
'scroll.vertically?{"count":1}': 'j', |
||||
'scroll.vertically?{"count":-1}': 'j', |
||||
} |
||||
}; |
||||
let value = values.valueFromForm(form); |
||||
|
||||
expect(value.keymaps).to.have.key('j'); |
||||
}); |
||||
|
||||
it('override search engine', () => { |
||||
let form = { |
||||
search: { |
||||
default: 'google', |
||||
engines: [ |
||||
['google', 'https://google.com/search?q={}'], |
||||
['google', 'https://google.co.jp/search?q={}'], |
||||
] |
||||
} |
||||
}; |
||||
let value = values.valueFromForm(form); |
||||
|
||||
expect(value.search.engines).to.have.property('google', 'https://google.co.jp/search?q={}'); |
||||
}); |
||||
}); |
||||
|
||||
describe('jsonFromValue', () => { |
||||
}); |
||||
|
||||
describe('formFromValue', () => { |
||||
it('convert empty value to form', () => { |
||||
let value = {}; |
||||
let form = values.formFromValue(value); |
||||
|
||||
expect(value).to.not.have.key('keymaps'); |
||||
expect(value).to.not.have.key('search'); |
||||
expect(value).to.not.have.key('blacklist'); |
||||
}); |
||||
|
||||
it('convert value to form', () => { |
||||
let value = { |
||||
keymaps: { |
||||
j: { type: 'scroll.vertically', count: 1 }, |
||||
JJ: { type: 'scroll.vertically', count: 100 }, |
||||
0: { type: 'scroll.home' }, |
||||
}, |
||||
search: { default: 'google', engines: { google: 'https://google.com/search?q={}' }}, |
||||
blacklist: [ '*.slack.com'] |
||||
}; |
||||
let form = values.formFromValue(value); |
||||
|
||||
expect(form.keymaps).to.have.property('scroll.vertically?{"count":1}', 'j'); |
||||
expect(form.keymaps).to.have.property('scroll.home', '0'); |
||||
expect(Object.keys(form.keymaps)).to.have.lengthOf(2); |
||||
expect(form.search).to.have.property('default', 'google'); |
||||
expect(form.search).to.have.deep.property('engines', [['google', 'https://google.com/search?q={}']]); |
||||
expect(form.blacklist).to.have.lengthOf(1); |
||||
expect(form.blacklist).to.include('*.slack.com'); |
||||
}); |
||||
}); |
||||
}); |
Reference in new issue