Merge pull request #253 from ueokande/qa-0.7

QA 0.7
jh-changes
Shin'ya Ueoka 7 years ago committed by GitHub
commit bd9501684d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 98
      QA.md
  2. 17
      src/settings/components/form/keymaps-form.jsx
  3. 8
      src/settings/components/form/keymaps-form.scss
  4. 65
      src/settings/components/index.jsx
  5. 67
      src/shared/settings/default.js
  6. 14
      src/shared/settings/values.js
  7. 4
      test/shared/settings/values.test.js

98
QA.md

@ -1,12 +1,12 @@
## Checklist for testing Vim Vixen ## Checklist for testing Vim Vixen
### Operations ### Keybindings in JSON settings
Test operations with default key maps. Test operations with default key maps.
#### Scrolling #### Scrolling
- [ ] <kbd>k</kbd> or <kbd>Ctrl</kbd>+<kbd>Y</kbd>, <kbd>j</kbd> or <kbd>Ctrl</kbd>+<kbd>E</kbd>: scroll up and down - [ ] <kbd>k</kbd>, <kbd>j</kbd>: scroll up and down
- [ ] <kbd>h</kbd>, <kbd>l</kbd>: scroll left and right - [ ] <kbd>h</kbd>, <kbd>l</kbd>: scroll left and right
- [ ] <kbd>Ctrl</kbd>+<kbd>U</kbd>, <kbd>Ctrl</kbd>+<kbd>D</kbd>: scroll up and down by half of screen - [ ] <kbd>Ctrl</kbd>+<kbd>U</kbd>, <kbd>Ctrl</kbd>+<kbd>D</kbd>: scroll up and down by half of screen
- [ ] <kbd>Ctrl</kbd>+<kbd>B</kbd>, <kbd>Ctrl</kbd>+<kbd>F</kbd>: scroll up and down by a screen - [ ] <kbd>Ctrl</kbd>+<kbd>B</kbd>, <kbd>Ctrl</kbd>+<kbd>F</kbd>: scroll up and down by a screen
@ -48,6 +48,55 @@ The behaviors of the console are tested in [Console section](#consoles).
- [ ] <kbd>y</kbd>: yank current URL and show a message - [ ] <kbd>y</kbd>: yank current URL and show a message
- [ ] Toggle enabled/disabled of plugin bu <kbd>Shift</kbd>+<kbd>Esc</kbd> - [ ] Toggle enabled/disabled of plugin bu <kbd>Shift</kbd>+<kbd>Esc</kbd>
### Keybindings in form settings
Test operations with default key maps.
#### Scrolling
- [ ] <kbd>k</kbd>, <kbd>j</kbd>: scroll up and down
- [ ] <kbd>h</kbd>, <kbd>l</kbd>: scroll left and right
- [ ] <kbd>Ctrl</kbd>+<kbd>U</kbd>, <kbd>Ctrl</kbd>+<kbd>D</kbd>: scroll up and down by half of screen
- [ ] <kbd>Ctrl</kbd>+<kbd>B</kbd>, <kbd>Ctrl</kbd>+<kbd>F</kbd>: scroll up and down by a screen
- [ ] <kbd>0</kbd>, <kbd>$</kbd>: scroll to leftmost and rightmost
- [ ] <kbd>g</kbd><kbd>g</kbd>, <kbd>G</kbd>: scroll to top and bottom
#### Console
The behaviors of the console are tested in [Console section](#consoles).
- [ ] <kbd>:</kbd>: open empty console
- [ ] <kbd>o</kbd>, <kbd>t</kbd>, <kbd>w</kbd>: open a console with `open`, `tabopen`, `winopen`
- [ ] <kbd>O</kbd>, <kbd>T</kbd>, <kbd>W</kbd>: open a console with `open`, `tabopen`, `winopen` and current URL
- [ ] <kbd>b</kbd>: open a consolw with `buffer`
#### Tabs
- [ ] <kbd>d</kbd>: delete current tab
- [ ] <kbd>u</kbd>: reopen close tab
- [ ] <kbd>K</kbd>, <kbd>J</kbd>: select prev and next tab
- [ ] <kbd>g0</kbd>, <kbd>g$</kbd>: select first and last tab
- [ ] <kbd>r</kbd>: reload current tab
- [ ] <kbd>R</kbd>: reload current tab without cache
- [ ] <kbd>zd</kbd>: duplicate current tab
- [ ] <kbd>zp</kbd>: toggle pin/unpin state on current tab
#### Navigation
- [ ] <kbd>H</kbd>, <kbd>L</kbd>: go back and forward in histories
- [ ] <kbd>[</kbd><kbd>[</kbd>, <kbd>]</kbd><kbd>]</kbd>: Open next/prev link in `<link>` tags.
- [ ] <kbd>[</kbd><kbd>[</kbd>, <kbd>]</kbd><kbd>]</kbd>: find prev and next links and open it
- [ ] <kbd>g</kbd><kbd>u</kbd>: go to parent directory
- [ ] <kbd>g</kbd><kbd>U</kbd>: go to root directory
#### Misc
- [ ] <kbd>z</kbd><kbd>i</kbd>, <kbd>z</kbd><kbd>o</kbd>: zoom-in and zoom-out
- [ ] <kbd>z</kbd><kbd>z</kbd>: set zoom level as default
- [ ] <kbd>y</kbd>: yank current URL and show a message
- [ ] Toggle enabled/disabled of plugin bu <kbd>Shift</kbd>+<kbd>Esc</kbd>
### Following links ### Following links
- [ ] <kbd>f</kbd>: start following links - [ ] <kbd>f</kbd>: start following links
@ -83,7 +132,7 @@ The behaviors of the console are tested in [Console section](#consoles).
- [ ] `buffer`,`buffer<SP>`: do nothing - [ ] `buffer`,`buffer<SP>`: do nothing
- [ ] `buffer <title>`, `buffer <url>`: select tab which has an title matched with - [ ] `buffer <title>`, `buffer <url>`: select tab which has an title matched with
- [ ] `buffer 1`: select leftmost tab - [ ] `buffer 1`: select leftmost tab
- [ ] `buffer 0`, `buffer 99`: shows an error - [ ] `buffer 0`, `buffer <a number more than count of tabs>`: shows an error
- [ ] select tabs rotationally when more than two tabs are matched - [ ] select tabs rotationally when more than two tabs are matched
### Completions ### Completions
@ -110,20 +159,22 @@ The behaviors of the console are tested in [Console section](#consoles).
### Settings ### Settings
#### Validations #### JSON Settings
##### Validations
- [ ] show error on invalid json - [ ] show error on invalid json
- [ ] show error when top-level keys has keys other than `keymaps`, `search`, and `blacklist` - [ ] show error when top-level keys has keys other than `keymaps`, `search`, and `blacklist`
##### `"keymaps"` section ###### `"keymaps"` section
- [ ] show error on unknown operation name in `"keymaps"` - [ ] show error on unknown operation name in `"keymaps"`
##### `"search"` section ###### `"search"` section
- validations in `"search"` section are not tested in this release - validations in `"search"` section are not tested in this release
#### `"blacklist"` section ##### `"blacklist"` section
- [ ] `github.com/a` blocks `github.com/a`, and not blocks `github.com/aa` - [ ] `github.com/a` blocks `github.com/a`, and not blocks `github.com/aa`
- [ ] `github.com/a*` blocks both `github.com/a` and `github.com/aa` - [ ] `github.com/a*` blocks both `github.com/a` and `github.com/aa`
@ -131,13 +182,44 @@ The behaviors of the console are tested in [Console section](#consoles).
- [ ] `github.com` blocks both `github.com/` and `github.com/a` - [ ] `github.com` blocks both `github.com/` and `github.com/a`
- [ ] `*.github.com` blocks `gist.github.com/`, and not `github.com` - [ ] `*.github.com` blocks `gist.github.com/`, and not `github.com`
#### Updating ##### Updating
- [ ] changes are updated on textarea blure when no errors - [ ] changes are updated on textarea blure when no errors
- [ ] changes are not updated on textarea blure when errors occurs - [ ] changes are not updated on textarea blure when errors occurs
- [ ] keymap settings are applied to open tabs without reload - [ ] keymap settings are applied to open tabs without reload
- [ ] search settings are applied to open tabs without reload - [ ] search settings are applied to open tabs without reload
#### Form Settings
<!-- validation on form settings does not implement in 0.7 -->
##### Search Engines
- [ ] able to change default
- [ ] able to remove item
- [ ] able to add item
##### `"blacklist"` section
- [ ] able to add item
- [ ] able to remove item
- [ ] `github.com/a` blocks `github.com/a`, and not blocks `github.com/aa`
- [ ] `github.com/a*` blocks both `github.com/a` and `github.com/aa`
- [ ] `github.com/` blocks `github.com/`, and not blocks `github.com/a`
- [ ] `github.com` blocks both `github.com/` and `github.com/a`
- [ ] `*.github.com` blocks `gist.github.com/`, and not `github.com`
##### Updating
- [ ] keymap settings are applied to open tabs without reload
- [ ] search settings are applied to open tabs without reload
### Settings source
- [ ] show confirmation dialog on switched from json to form
- [ ] state is saved on source changed
- [ ] on switching form -> json -> form, first and last form setting is equivalent to first one
### For certain sites ### For certain sites
- [ ] scoll on Hacker News - [ ] scoll on Hacker News

@ -8,8 +8,10 @@ const KeyMapFields = [
['scroll.vertically?{"count":-1}', 'Scroll up'], ['scroll.vertically?{"count":-1}', 'Scroll up'],
['scroll.horizonally?{"count":-1}', 'Scroll left'], ['scroll.horizonally?{"count":-1}', 'Scroll left'],
['scroll.horizonally?{"count":1}', 'Scroll right'], ['scroll.horizonally?{"count":1}', 'Scroll right'],
['scroll.home', 'Scroll leftmost'], ['scroll.home', 'Scroll to leftmost'],
['scroll.end', 'Scroll last'], ['scroll.end', 'Scroll to rightmost'],
['scroll.top', 'Scroll to top'],
['scroll.bottom', 'Scroll to bottom'],
['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":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'],
@ -21,7 +23,8 @@ const KeyMapFields = [
['tabs.prev?{"count":1}', 'Select prev Tab'], ['tabs.prev?{"count":1}', 'Select prev Tab'],
['tabs.first', 'Select first tab'], ['tabs.first', 'Select first tab'],
['tabs.last', 'Select last tab'], ['tabs.last', 'Select last tab'],
['tabs.reload?{"cache":true}', 'Reload current tab'], ['tabs.reload?{"cache":false}', 'Reload current tab'],
['tabs.reload?{"cache":true}', 'Reload with no caches'],
['tabs.pin.toggle', 'Toggle pinned state'], ['tabs.pin.toggle', 'Toggle pinned state'],
['tabs.duplicate', 'Dupplicate a tab'], ['tabs.duplicate', 'Dupplicate a tab'],
], [ ], [
@ -55,6 +58,8 @@ const KeyMapFields = [
] ]
]; ];
const AllowdOps = [].concat(...KeyMapFields.map(group => group.map(e => e[0])));
class KeymapsForm extends Component { class KeymapsForm extends Component {
render() { render() {
@ -62,10 +67,10 @@ class KeymapsForm extends Component {
if (!values) { if (!values) {
values = {}; values = {};
} }
return <div className='keymap-fields'> return <div className='form-keymaps-form'>
{ {
KeyMapFields.map((group, index) => { KeyMapFields.map((group, index) => {
return <div key={index} className='form-keymaps-form'> return <div key={index} className='form-keymaps-form-field-group'>
{ {
group.map((field) => { group.map((field) => {
let name = field[0]; let name = field[0];
@ -96,4 +101,6 @@ class KeymapsForm extends Component {
} }
} }
KeymapsForm.AllowdOps = AllowdOps;
export default KeymapsForm; export default KeymapsForm;

@ -1,9 +1,11 @@
.form-keymaps-form { .form-keymaps-form {
column-count: 3; column-count: 3;
.keymap-fields-group {
&-field-group {
margin-top: 24px; margin-top: 24px;
} }
.keymap-fields-group:first-of-type {
margin-top: 0; &-field-group:first-of-type {
margin-top: 24px;
} }
} }

@ -123,6 +123,18 @@ class SettingsComponent extends Component {
} }
} }
validateValue(e) {
let next = Object.assign({}, this.state);
next.errors.json = '';
try {
this.validate(e.target);
} catch (err) {
next.errors.json = err.message;
}
next.settings[e.target.name] = e.target.value;
}
bindForm(name, value) { bindForm(name, value) {
let next = Object.assign({}, this.state, { let next = Object.assign({}, this.state, {
settings: Object.assign({}, this.state.settings, { settings: Object.assign({}, this.state.settings, {
@ -136,41 +148,68 @@ class SettingsComponent extends Component {
bindValue(e) { bindValue(e) {
let next = Object.assign({}, this.state); let next = Object.assign({}, this.state);
let error = false;
next.errors.json = ''; next.errors.json = '';
try { try {
this.validate(e.target); this.validate(e.target);
} catch (err) { } catch (err) {
next.errors.json = err.message; next.errors.json = err.message;
error = true;
} }
next.settings[e.target.name] = e.target.value; next.settings[e.target.name] = e.target.value;
this.setState(next); this.setState(this.state);
if (!error) {
this.context.store.dispatch(settingActions.save(next.settings)); this.context.store.dispatch(settingActions.save(next.settings));
} }
}
bindSource(e) { migrateToForm() {
let from = this.state.settings.source;
let to = e.target.value;
let next = Object.assign({}, this.state);
if (from === 'form' && to === 'json') {
next.settings.json =
settingsValues.jsonFromForm(this.state.settings.form);
} else if (from === 'json' && to === 'form') {
let b = window.confirm(DO_YOU_WANT_TO_CONTINUE); let b = window.confirm(DO_YOU_WANT_TO_CONTINUE);
if (!b) { if (!b) {
this.setState(this.state); this.setState(this.state);
return; return;
} }
next.settings.form = try {
settingsValues.formFromJson(this.state.settings.json); validator.validate(JSON.parse(this.state.settings.json));
} catch (err) {
this.setState(this.state);
return;
}
let form = settingsValues.formFromJson(
this.state.settings.json, KeymapsForm.AllowdOps);
let next = Object.assign({}, this.state);
next.settings.form = form;
next.settings.source = 'form';
next.errors.json = '';
this.setState(next);
this.context.store.dispatch(settingActions.save(next.settings));
} }
next.settings.source = to;
migrateToJson() {
let json = settingsValues.jsonFromForm(this.state.settings.form);
let next = Object.assign({}, this.state);
next.settings.json = json;
next.settings.source = 'json';
next.errors.json = '';
this.setState(next); this.setState(next);
this.context.store.dispatch(settingActions.save(next.settings)); this.context.store.dispatch(settingActions.save(next.settings));
} }
bindSource(e) {
let from = this.state.settings.source;
let to = e.target.value;
if (from === 'form' && to === 'json') {
this.migrateToJson();
} else if (from === 'json' && to === 'form') {
this.migrateToForm();
}
}
} }
export default SettingsComponent; export default SettingsComponent;

@ -15,8 +15,6 @@ export default {
"j": { "type": "scroll.vertically", "count": 1 }, "j": { "type": "scroll.vertically", "count": 1 },
"h": { "type": "scroll.horizonally", "count": -1 }, "h": { "type": "scroll.horizonally", "count": -1 },
"l": { "type": "scroll.horizonally", "count": 1 }, "l": { "type": "scroll.horizonally", "count": 1 },
"<C-Y>": { "type": "scroll.vertically", "count": -1 },
"<C-E>": { "type": "scroll.vertically", "count": 1 },
"<C-U>": { "type": "scroll.pages", "count": -0.5 }, "<C-U>": { "type": "scroll.pages", "count": -0.5 },
"<C-D>": { "type": "scroll.pages", "count": 0.5 }, "<C-D>": { "type": "scroll.pages", "count": 0.5 },
"<C-B>": { "type": "scroll.pages", "count": -1 }, "<C-B>": { "type": "scroll.pages", "count": -1 },
@ -63,69 +61,4 @@ export default {
} }
} }
}`, }`,
'form': {
'keymaps': {
'scroll.vertically?{"count":1}': 'j',
'scroll.vertically?{"count":-1}': 'k',
'scroll.horizonally?{"count":-1}': 'h',
'scroll.horizonally?{"count":1}': 'l',
'scroll.home': '0',
'scroll.end': '$',
'scroll.pages?{"count":-0.5}': '<C-U>',
'scroll.pages?{"count":0.5}': '<C-D>',
'scroll.pages?{"count":-1}': '<C-B>',
'scroll.pages?{"count":1}': '<C-F>',
'tabs.close': 'd',
'tabs.reopen': 'u',
'tabs.next?{"count":1}': 'J',
'tabs.prev?{"count":1}': 'K',
'tabs.first': 'g0',
'tabs.last': 'g$',
'tabs.reload?{"cache":true}': 'r',
'tabs.pin.toggle': 'zp',
'tabs.duplicate': 'zd',
'follow.start?{"newTab":false}': 'f',
'follow.start?{"newTab":true}': 'F',
'navigate.history.prev': 'H',
'navigate.history.next': 'L',
'navigate.link.next': ']]',
'navigate.link.prev': '[[',
'navigate.parent': 'gu',
'navigate.root': 'gU',
'find.start': '/',
'find.next': 'n',
'find.prev': 'N',
'command.show': ':',
'command.show.open?{"alter":false}': 'o',
'command.show.open?{"alter":true}': 'O',
'command.show.tabopen?{"alter":false}': 't',
'command.show.tabopen?{"alter":true}': 'T',
'command.show.winopen?{"alter":false}': 'w',
'command.show.winopen?{"alter":true}': 'W',
'command.show.buffer': 'b',
'addon.toggle.enabled': '<S-Esc>',
'urls.yank': 'y',
'zoom.in': 'zi',
'zoom.out': 'zo',
'zoom.neutral': 'zz',
},
'search': {
'default': 'google',
'engines': [
['google', 'https,//google.com/search?q={}'],
['yahoo', 'https,//search.yahoo.com/search?p={}'],
['bing', 'https,//www.bing.com/search?q={}'],
['duckduckgo', 'https,//duckduckgo.com/?q={}'],
['twitter', 'https,//twitter.com/search?q={}'],
['wikipedia', 'https,//en.wikipedia.org/w/index.php?search={}'],
]
},
'blacklist': [],
}
}; };

@ -1,5 +1,3 @@
import DefaultSettings from './default';
const operationFromFormName = (name) => { const operationFromFormName = (name) => {
let [type, argStr] = name.split('?'); let [type, argStr] = name.split('?');
let args = {}; let args = {};
@ -55,16 +53,16 @@ const jsonFromValue = (value) => {
return JSON.stringify(value, undefined, 2); return JSON.stringify(value, undefined, 2);
}; };
const formFromValue = (value) => { const formFromValue = (value, allowedOps) => {
let keymaps = undefined; let keymaps = undefined;
if (value.keymaps) { if (value.keymaps) {
let allowedOps = new Set(Object.keys(DefaultSettings.form.keymaps)); let allowedSet = new Set(allowedOps);
keymaps = {}; keymaps = {};
for (let keys of Object.keys(value.keymaps)) { for (let keys of Object.keys(value.keymaps)) {
let op = operationToFormName(value.keymaps[keys]); let op = operationToFormName(value.keymaps[keys]);
if (allowedOps.has(op)) { if (allowedSet.has(op)) {
keymaps[op] = keys; keymaps[op] = keys;
} }
} }
@ -89,9 +87,9 @@ const jsonFromForm = (form) => {
return jsonFromValue(valueFromForm(form)); return jsonFromValue(valueFromForm(form));
}; };
const formFromJson = (json) => { const formFromJson = (json, allowedOps) => {
let value = valueFromJson(json); let value = valueFromJson(json);
return formFromValue(value); return formFromValue(value, allowedOps);
}; };
export { export {

@ -98,9 +98,11 @@ describe("settings values", () => {
search: { default: 'google', engines: { google: 'https://google.com/search?q={}' }}, search: { default: 'google', engines: { google: 'https://google.com/search?q={}' }},
blacklist: [ '*.slack.com'] blacklist: [ '*.slack.com']
}; };
let form = values.formFromValue(value); let allowed = ['scroll.vertically?{"count":1}', 'scroll.home' ];
let form = values.formFromValue(value, allowed);
expect(form.keymaps).to.have.property('scroll.vertically?{"count":1}', 'j'); expect(form.keymaps).to.have.property('scroll.vertically?{"count":1}', 'j');
expect(form.keymaps).to.not.have.property('scroll.vertically?{"count":100}');
expect(form.keymaps).to.have.property('scroll.home', '0'); expect(form.keymaps).to.have.property('scroll.home', '0');
expect(Object.keys(form.keymaps)).to.have.lengthOf(2); expect(Object.keys(form.keymaps)).to.have.lengthOf(2);
expect(form.search).to.have.property('default', 'google'); expect(form.search).to.have.property('default', 'google');