diff --git a/QA.md b/QA.md index 8cba39f..404f50f 100644 --- a/QA.md +++ b/QA.md @@ -1,12 +1,12 @@ ## Checklist for testing Vim Vixen -### Operations +### Keybindings in JSON settings Test operations with default key maps. #### Scrolling -- [ ] k or Ctrl+Y, j or Ctrl+E: scroll up and down +- [ ] k, j: scroll up and down - [ ] h, l: scroll left and right - [ ] Ctrl+U, Ctrl+D: scroll up and down by half of screen - [ ] Ctrl+B, Ctrl+F: scroll up and down by a screen @@ -48,6 +48,55 @@ The behaviors of the console are tested in [Console section](#consoles). - [ ] y: yank current URL and show a message - [ ] Toggle enabled/disabled of plugin bu Shift+Esc +### Keybindings in form settings + +Test operations with default key maps. + +#### Scrolling + +- [ ] k, j: scroll up and down +- [ ] h, l: scroll left and right +- [ ] Ctrl+U, Ctrl+D: scroll up and down by half of screen +- [ ] Ctrl+B, Ctrl+F: scroll up and down by a screen +- [ ] 0, $: scroll to leftmost and rightmost +- [ ] gg, G: scroll to top and bottom + +#### Console + +The behaviors of the console are tested in [Console section](#consoles). + +- [ ] :: open empty console +- [ ] o, t, w: open a console with `open`, `tabopen`, `winopen` +- [ ] O, T, W: open a console with `open`, `tabopen`, `winopen` and current URL +- [ ] b: open a consolw with `buffer` + +#### Tabs + +- [ ] d: delete current tab +- [ ] u: reopen close tab +- [ ] K, J: select prev and next tab +- [ ] g0, g$: select first and last tab +- [ ] r: reload current tab +- [ ] R: reload current tab without cache +- [ ] zd: duplicate current tab +- [ ] zp: toggle pin/unpin state on current tab + +#### Navigation + +- [ ] H, L: go back and forward in histories +- [ ] [[, ]]: Open next/prev link in `` tags. +- [ ] [[, ]]: find prev and next links and open it +- [ ] gu: go to parent directory +- [ ] gU: go to root directory + +#### Misc + +- [ ] zi, zo: zoom-in and zoom-out +- [ ] zz: set zoom level as default +- [ ] y: yank current URL and show a message +- [ ] Toggle enabled/disabled of plugin bu Shift+Esc + + ### Following links - [ ] f: start following links @@ -83,7 +132,7 @@ The behaviors of the console are tested in [Console section](#consoles). - [ ] `buffer`,`buffer`: do nothing - [ ] `buffer `, `buffer <url>`: select tab which has an title matched with - [ ] `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 ### Completions @@ -110,20 +159,22 @@ The behaviors of the console are tested in [Console section](#consoles). ### Settings -#### Validations +#### JSON Settings + +##### Validations - [ ] show error on invalid json - [ ] 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"` -##### `"search"` section +###### `"search"` section - 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 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 `gist.github.com/`, and not `github.com` -#### Updating +##### Updating - [ ] changes are updated on textarea blure when no errors - [ ] changes are not updated on textarea blure when errors occurs - [ ] keymap 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 - [ ] scoll on Hacker News diff --git a/src/settings/components/form/keymaps-form.jsx b/src/settings/components/form/keymaps-form.jsx index f64320c..f3b6abe 100644 --- a/src/settings/components/form/keymaps-form.jsx +++ b/src/settings/components/form/keymaps-form.jsx @@ -8,8 +8,10 @@ const KeyMapFields = [ ['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.home', 'Scroll to leftmost'], + ['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":-1}', 'Scroll up by a screen'], @@ -21,7 +23,8 @@ const KeyMapFields = [ ['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.reload?{"cache":false}', 'Reload current tab'], + ['tabs.reload?{"cache":true}', 'Reload with no caches'], ['tabs.pin.toggle', 'Toggle pinned state'], ['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 { render() { @@ -62,10 +67,10 @@ class KeymapsForm extends Component { if (!values) { values = {}; } - return <div className='keymap-fields'> + return <div className='form-keymaps-form'> { 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) => { let name = field[0]; @@ -96,4 +101,6 @@ class KeymapsForm extends Component { } } +KeymapsForm.AllowdOps = AllowdOps; + export default KeymapsForm; diff --git a/src/settings/components/form/keymaps-form.scss b/src/settings/components/form/keymaps-form.scss index 3a83910..1a4e5cd 100644 --- a/src/settings/components/form/keymaps-form.scss +++ b/src/settings/components/form/keymaps-form.scss @@ -1,9 +1,11 @@ .form-keymaps-form { column-count: 3; - .keymap-fields-group { + + &-field-group { margin-top: 24px; } - .keymap-fields-group:first-of-type { - margin-top: 0; + + &-field-group:first-of-type { + margin-top: 24px; } } diff --git a/src/settings/components/index.jsx b/src/settings/components/index.jsx index 3961982..73520ca 100644 --- a/src/settings/components/index.jsx +++ b/src/settings/components/index.jsx @@ -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) { let next = Object.assign({}, this.state, { settings: Object.assign({}, this.state.settings, { @@ -136,15 +148,54 @@ class SettingsComponent extends Component { bindValue(e) { let next = Object.assign({}, this.state); + let error = false; next.errors.json = ''; try { this.validate(e.target); } catch (err) { next.errors.json = err.message; + error = true; } next.settings[e.target.name] = e.target.value; + this.setState(this.state); + if (!error) { + this.context.store.dispatch(settingActions.save(next.settings)); + } + } + + migrateToForm() { + let b = window.confirm(DO_YOU_WANT_TO_CONTINUE); + if (!b) { + this.setState(this.state); + return; + } + try { + 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)); + } + + 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.context.store.dispatch(settingActions.save(next.settings)); } @@ -153,23 +204,11 @@ class SettingsComponent extends Component { 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); + this.migrateToJson(); } else if (from === 'json' && to === 'form') { - let b = window.confirm(DO_YOU_WANT_TO_CONTINUE); - if (!b) { - this.setState(this.state); - return; - } - next.settings.form = - settingsValues.formFromJson(this.state.settings.json); + this.migrateToForm(); } - next.settings.source = to; - - this.setState(next); - this.context.store.dispatch(settingActions.save(next.settings)); } } diff --git a/src/shared/settings/default.js b/src/shared/settings/default.js index 69238e3..d187565 100644 --- a/src/shared/settings/default.js +++ b/src/shared/settings/default.js @@ -15,8 +15,6 @@ export default { "j": { "type": "scroll.vertically", "count": 1 }, "h": { "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-D>": { "type": "scroll.pages", "count": 0.5 }, "<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': [], - } }; diff --git a/src/shared/settings/values.js b/src/shared/settings/values.js index 4482fbb..4e55fa0 100644 --- a/src/shared/settings/values.js +++ b/src/shared/settings/values.js @@ -1,5 +1,3 @@ -import DefaultSettings from './default'; - const operationFromFormName = (name) => { let [type, argStr] = name.split('?'); let args = {}; @@ -55,16 +53,16 @@ const jsonFromValue = (value) => { return JSON.stringify(value, undefined, 2); }; -const formFromValue = (value) => { - +const formFromValue = (value, allowedOps) => { let keymaps = undefined; + if (value.keymaps) { - let allowedOps = new Set(Object.keys(DefaultSettings.form.keymaps)); + let allowedSet = new Set(allowedOps); keymaps = {}; for (let keys of Object.keys(value.keymaps)) { let op = operationToFormName(value.keymaps[keys]); - if (allowedOps.has(op)) { + if (allowedSet.has(op)) { keymaps[op] = keys; } } @@ -89,9 +87,9 @@ const jsonFromForm = (form) => { return jsonFromValue(valueFromForm(form)); }; -const formFromJson = (json) => { +const formFromJson = (json, allowedOps) => { let value = valueFromJson(json); - return formFromValue(value); + return formFromValue(value, allowedOps); }; export { diff --git a/test/shared/settings/values.test.js b/test/shared/settings/values.test.js index 2c222b6..2632cd7 100644 --- a/test/shared/settings/values.test.js +++ b/test/shared/settings/values.test.js @@ -98,9 +98,11 @@ describe("settings values", () => { search: { default: 'google', engines: { google: 'https://google.com/search?q={}' }}, 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.not.have.property('scroll.vertically?{"count":100}'); 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');