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 { |
.vimvixen-settings-form { |
||||||
|
padding: 2px; |
||||||
|
|
||||||
textarea[name=json] { |
textarea[name=json] { |
||||||
font-family: monospace; |
font-family: monospace; |
||||||
width: 100%; |
width: 100%; |
||||||
min-height: 64ex; |
min-height: 64ex; |
||||||
resize: vertical; |
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 { h, Component } from 'preact'; |
||||||
import PropTypes from 'prop-types'; |
|
||||||
|
|
||||||
class Provider extends React.PureComponent { |
class Provider extends Component { |
||||||
getChildContext() { |
getChildContext() { |
||||||
return { store: this.props.store }; |
return { store: this.props.store }; |
||||||
} |
} |
||||||
|
|
||||||
render() { |
render() { |
||||||
return React.Children.only(this.props.children); |
return <div> |
||||||
|
{ this.props.children } |
||||||
|
</div>; |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
Provider.childContextTypes = { |
|
||||||
store: PropTypes.any, |
|
||||||
}; |
|
||||||
|
|
||||||
export default Provider; |
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