diff --git a/app/renderer/components/menubar.js b/app/renderer/components/menubar.js index c4dcf663925..94120dbb726 100644 --- a/app/renderer/components/menubar.js +++ b/app/renderer/components/menubar.js @@ -53,22 +53,19 @@ class MenubarItem extends ImmutableComponent { } // If clicking on an already selected item, deselect it const selected = this.props.menubar.props.selectedIndex - ? this.props.menubar.props.selectedIndex[0] - : null if (selected && selected === this.props.index) { windowActions.setContextMenuDetail() - windowActions.setSubmenuSelectedIndex() + windowActions.setMenuBarSelectedIndex() return } // Otherwise, mark item as selected and show its context menu - windowActions.setSubmenuSelectedIndex([this.props.index]) + windowActions.setMenuBarSelectedIndex(this.props.index) + windowActions.setContextMenuSelectedIndex([0]) const rect = e.target.getBoundingClientRect() showContextMenu(rect, this.props.submenu, this.props.lastFocusedSelector) } onMouseOver (e) { const selected = this.props.menubar.props.selectedIndex - ? this.props.menubar.props.selectedIndex[0] - : null if (typeof selected === 'number' && selected !== this.props.index) { this.onClick(e) } @@ -101,196 +98,75 @@ class Menubar extends ImmutableComponent { document.removeEventListener('keydown', this.onKeyDown) } - /** - * Used to get the submenu of a top level menu like File, Edit, etc. - * Index will default to the selected menu if not provided / valid. - */ - getTemplate (index) { - if (typeof index !== 'number') index = this.props.selectedIndex[0] - return this.props.template.get(index).get('submenu') - } - /** - * Same as getTemplate but excluding line separators and items that are not visible. - */ - getTemplateItemsOnly (index) { - return this.getTemplate(index).filter((element) => { - if (element.get('type') === separatorMenuItem.type) return false - if (element.has('visible')) return element.get('visible') - return true - }) - } /** * Get client rect for the MenubarItem controls. * Used to position the context menu object. */ getMenubarItemBounds (index) { - if (typeof index !== 'number') index = this.props.selectedIndex[0] + if (typeof index !== 'number') index = this.props.selectedIndex const selected = document.querySelectorAll('.menubar .menubarItem[data-index=\'' + index + '\']') if (selected.length === 1) { return selected.item(0).getBoundingClientRect() } return null } - /** - * Get client rect for the actively selected ContextMenu. - * Used to position the child menu if parent has children. - */ - getContextMenuItemBounds () { - const selected = document.querySelectorAll('.contextMenuItem.selectedByKeyboard') - if (selected.length > 0) { - return selected.item(selected.length - 1).getBoundingClientRect() - } - return null - } - /** - * Returns index for the active / focused menu. - */ - get currentIndex () { - return this.props.selectedIndex[this.props.selectedIndex.length - 1] - } - /** - * Upper bound for the active / focused menu. - */ - get maxIndex () { - return this.getMenuByIndex().size - 1 - } - /** - * Returns true is current state is inside a regular menu. - */ - get hasMenuSelection () { - return this.props.selectedIndex.length > 1 - } - /** - * Returns true if current state is inside a submenu. - */ - get hasSubmenuSelection () { - return this.props.selectedIndex.length > 2 - } - /** - * Fetch menu based on selected index. - * Will navigate children to find nested menus. - */ - getMenuByIndex (parentItem, currentDepth) { - if (!parentItem) parentItem = this.getTemplateItemsOnly() - if (!currentDepth) currentDepth = 0 - - const selectedIndices = this.props.selectedIndex.slice(1) - if (selectedIndices.length === 0) return parentItem - - const submenuIndex = selectedIndices[currentDepth] - const childItem = parentItem.get(submenuIndex) - - if (childItem && childItem.get('submenu') && currentDepth < (selectedIndices.length - 1)) { - return this.getMenuByIndex(childItem.get('submenu'), currentDepth + 1) - } - - return parentItem - } onKeyDown (e) { const selectedIndex = this.props.selectedIndex + const template = this.props.template + const contextMenuIndex = this.props.contextMenuSelectedIndex - if (!selectedIndex || !this.props.template) return + if (!template) return switch (e.which) { - case keyCodes.ENTER: - e.preventDefault() - const selectedLabel = this.getMenuByIndex().getIn([this.currentIndex, 'label']) - windowActions.clickMenubarSubmenu(selectedLabel) - windowActions.resetMenuState() - break - case keyCodes.LEFT: case keyCodes.RIGHT: - e.preventDefault() - - // Left arrow inside a submenu - // <= go back one level - if (e.which === keyCodes.LEFT && this.hasSubmenuSelection) { - const newIndices = selectedIndex.slice() - newIndices.pop() - windowActions.setSubmenuSelectedIndex(newIndices) - - let openedSubmenuDetails = this.props.contextMenuDetail.get('openedSubmenuDetails') - ? this.props.contextMenuDetail.get('openedSubmenuDetails') - : new Immutable.List() - openedSubmenuDetails = openedSubmenuDetails.pop() - - windowActions.setContextMenuDetail(this.props.contextMenuDetail.set('openedSubmenuDetails', openedSubmenuDetails)) - break + if (contextMenuIndex !== null) { + const currentTemplate = template.get(selectedIndex).get('submenu').filter((element) => { + if (element.get('type') === separatorMenuItem.type) return false + if (element.has('visible')) return element.get('visible') + return true + }).get(contextMenuIndex[0]) + + if (currentTemplate && currentTemplate.has('submenu')) { + break + } } - const selectedMenuItem = selectedIndex - ? this.getMenuByIndex().get(this.currentIndex) - : null - - // Right arrow on a menu item which has a submenu - // => go up one level (default next menu to item 0) - if (e.which === keyCodes.RIGHT && selectedMenuItem && selectedMenuItem.has('submenu')) { - const newIndices = selectedIndex.slice() - newIndices.push(0) - windowActions.setSubmenuSelectedIndex(newIndices) - - let openedSubmenuDetails = this.props.contextMenuDetail.get('openedSubmenuDetails') - ? this.props.contextMenuDetail.get('openedSubmenuDetails') - : new Immutable.List() - - const rect = this.getContextMenuItemBounds() - const itemHeight = (rect.bottom - rect.top) - - openedSubmenuDetails = openedSubmenuDetails.push(Immutable.fromJS({ - y: (rect.top - itemHeight), - template: selectedMenuItem.get('submenu') - })) - - windowActions.setContextMenuDetail(this.props.contextMenuDetail.set('openedSubmenuDetails', openedSubmenuDetails)) - break - } + e.preventDefault() + e.stopPropagation() // Regular old menu item const nextIndex = selectedIndex === null ? 0 : wrappingClamp( - selectedIndex[0] + (e.which === keyCodes.LEFT ? -1 : 1), + selectedIndex + (e.which === keyCodes.LEFT ? -1 : 1), 0, this.props.template.size - 1) - // Context menu already being displayed; auto-open the next one + windowActions.setMenuBarSelectedIndex(nextIndex) + if (this.props.contextMenuDetail) { - windowActions.setSubmenuSelectedIndex([nextIndex, 0]) - showContextMenu(this.getMenubarItemBounds(nextIndex), this.getTemplate(nextIndex).toJS(), this.props.lastFocusedSelector) + windowActions.setContextMenuSelectedIndex([0]) + showContextMenu(this.getMenubarItemBounds(nextIndex), template.get(nextIndex).get('submenu').toJS(), this.props.lastFocusedSelector) } else { - windowActions.setSubmenuSelectedIndex([nextIndex]) + windowActions.setContextMenuSelectedIndex() } break - case keyCodes.UP: case keyCodes.DOWN: + case keyCodes.ENTER: e.preventDefault() - if (this.getTemplateItemsOnly()) { - if (!this.props.contextMenuDetail) { - // First time hitting up/down; popup the context menu - const newIndices = selectedIndex.slice() - newIndices.push(0) - windowActions.setSubmenuSelectedIndex(newIndices) - showContextMenu(this.getMenubarItemBounds(), this.getTemplate().toJS(), this.props.lastFocusedSelector) - } else { - // Context menu already visible; move selection up or down - const nextIndex = wrappingClamp( - this.currentIndex + (e.which === keyCodes.UP ? -1 : 1), - 0, - this.maxIndex) - - const newIndices = selectedIndex.slice() - if (this.hasMenuSelection) { - newIndices[selectedIndex.length - 1] = nextIndex - } else { - newIndices.push(0) - } - windowActions.setSubmenuSelectedIndex(newIndices) - } + if (contextMenuIndex === null && template.get(selectedIndex).has('submenu')) { + e.stopPropagation() + windowActions.setContextMenuSelectedIndex([0]) + showContextMenu(this.getMenubarItemBounds(selectedIndex), template.get(selectedIndex).get('submenu').toJS(), this.props.lastFocusedSelector) } break + + case keyCodes.UP: + e.preventDefault() + } } shouldComponentUpdate (nextProps, nextState) { @@ -309,7 +185,7 @@ class Menubar extends ImmutableComponent { lastFocusedSelector: this.props.lastFocusedSelector, menubar: this } - if (this.props.selectedIndex && props.index === this.props.selectedIndex[0]) { + if (props.index === this.props.selectedIndex) { props.selected = true } return diff --git a/docs/windowActions.md b/docs/windowActions.md index d817b85702e..4aea42ff0d0 100644 --- a/docs/windowActions.md +++ b/docs/windowActions.md @@ -871,10 +871,10 @@ Used by `main.js` when click happens on content area (not on a link or react con -### setSubmenuSelectedIndex(index) +### setMenuBarSelectedIndex(index) (Windows only) -Used to track selected index of a context menu +Used to track selected index of a menu bar Needed because arrow keys can be used to navigate the custom menu **Parameters** @@ -884,6 +884,18 @@ Needed because arrow keys can be used to navigate the custom menu +### setContextMenuSelectedIndex(index) + +Used to track selected index of a context menu +Needed because arrow keys can be used to navigate the context menu + +**Parameters** + +**index**: `number`, zero based index of the item. + Index excludes menu separators and hidden items. + + + ### setLastFocusedSelector(selector) (Windows only at the moment) diff --git a/js/actions/windowActions.js b/js/actions/windowActions.js index 9fdea5b66eb..141768a6ec1 100644 --- a/js/actions/windowActions.js +++ b/js/actions/windowActions.js @@ -1108,14 +1108,27 @@ const windowActions = { /** * (Windows only) - * Used to track selected index of a context menu + * Used to track selected index of a menu bar * Needed because arrow keys can be used to navigate the custom menu * @param {number} index - zero based index of the item. * Index excludes menu separators and hidden items. */ - setSubmenuSelectedIndex: function (index) { + setMenuBarSelectedIndex: function (index) { + dispatch({ + actionType: windowConstants.WINDOW_SET_MENUBAR_SELECTED_INDEX, + index + }) + }, + + /** + * Used to track selected index of a context menu + * Needed because arrow keys can be used to navigate the context menu + * @param {number} index - zero based index of the item. + * Index excludes menu separators and hidden items. + */ + setContextMenuSelectedIndex: function (index) { dispatch({ - actionType: windowConstants.WINDOW_SET_SUBMENU_SELECTED_INDEX, + actionType: windowConstants.WINDOW_SET_CONTEXT_MENU_SELECTED_INDEX, index }) }, diff --git a/js/components/contextMenu.js b/js/components/contextMenu.js index 6cfd4612217..620518746f9 100644 --- a/js/components/contextMenu.js +++ b/js/components/contextMenu.js @@ -8,21 +8,11 @@ const ImmutableComponent = require('./immutableComponent') const windowActions = require('../actions/windowActions') const cx = require('../lib/classSet') const KeyCodes = require('../../app/common/constants/keyCodes') -const {formatAccelerator} = require('../../app/common/lib/formatUtil') +const {formatAccelerator, wrappingClamp} = require('../../app/common/lib/formatUtil') const separatorMenuItem = require('../../app/common/commonMenu').separatorMenuItem +const keyCodes = require('../../app/common/constants/keyCodes') class ContextMenuItem extends ImmutableComponent { - componentDidMount () { - window.addEventListener('keydown', this.onKeyDown) - } - componentWillUnmount () { - window.removeEventListener('keydown', this.onKeyDown) - } - onKeyDown (e) { - if (e.keyCode === KeyCodes.ESC || e.keyCode === KeyCodes.TAB) { - windowActions.setContextMenuDetail() - } - } get submenu () { return this.props.contextMenuItem.get('submenu') } @@ -100,10 +90,6 @@ class ContextMenuItem extends ImmutableComponent { } windowActions.setContextMenuDetail(this.props.contextMenuDetail.set('openedSubmenuDetails', openedSubmenuDetails)) } - onMouseLeave (e) { - if (this.hasSubmenu) { - } - } getLabelForItem (item) { const label = item.get('label') if (label) { @@ -170,7 +156,6 @@ class ContextMenuItem extends ImmutableComponent { onContextMenu={this.onContextMenu.bind(this)} disabled={this.props.contextMenuItem.get('enabled') === false} onMouseEnter={this.onMouseEnter.bind(this)} - onMouseLeave={this.onMouseLeave.bind(this)} onClick={this.onClick.bind(this, this.props.contextMenuItem.get('click'), true)}> { this.props.contextMenuItem.get('checked') @@ -252,13 +237,165 @@ class ContextMenuSingle extends ImmutableComponent { * Represents a context menu including all submenus */ class ContextMenu extends ImmutableComponent { + constructor () { + super() + this.onKeyDown = this.onKeyDown.bind(this) + } + + componentDidMount () { + window.addEventListener('keydown', this.onKeyDown) + window.addEventListener('keyup', this.onKeyUp) + } + + componentWillUnmount () { + window.removeEventListener('keydown', this.onKeyDown) + window.removeEventListener('keyup', this.onKeyUp) + } + + onKeyDown (e) { + const selectedIndex = (this.props.selectedIndex === null) ? [0] : this.props.selectedIndex + const currentIndex = selectedIndex[selectedIndex.length - 1] + const selectedTemplate = this.getMenuByIndex(selectedIndex, this.props.contextMenuDetail.get('template')) + const selectedMenuItem = selectedTemplate.get(currentIndex) + + switch (e.keyCode) { + case keyCodes.ENTER: + e.preventDefault() + e.stopPropagation() + const action = selectedTemplate.getIn([currentIndex, 'click']) + if (action) { + action(e) + } + windowActions.resetMenuState() + break + + case KeyCodes.ESC: + case KeyCodes.TAB: + windowActions.resetMenuState() + break + + case keyCodes.LEFT: + // Left arrow inside a sub menu + // <= go back one level + if (this.hasSubmenuSelection) { + const newIndices = selectedIndex.slice() + newIndices.pop() + windowActions.setContextMenuSelectedIndex(newIndices) + + let openedSubmenuDetails = this.props.contextMenuDetail.get('openedSubmenuDetails') + ? this.props.contextMenuDetail.get('openedSubmenuDetails') + : new Immutable.List() + openedSubmenuDetails = openedSubmenuDetails.pop() + + windowActions.setContextMenuDetail(this.props.contextMenuDetail.set('openedSubmenuDetails', openedSubmenuDetails)) + } + break + + case keyCodes.RIGHT: + // Right arrow on a menu item which has a sub menu + // => go up one level (default next menu to item 0) + const isSubMenu = !!selectedMenuItem.get('submenu') + + if (isSubMenu) { + e.stopPropagation() + const newIndices = selectedIndex.slice() + newIndices.push(0) + windowActions.setContextMenuSelectedIndex(newIndices) + + let openedSubmenuDetails = this.props.contextMenuDetail.get('openedSubmenuDetails') + ? this.props.contextMenuDetail.get('openedSubmenuDetails') + : new Immutable.List() + + const rect = this.getContextMenuItemBounds() + const itemHeight = (rect.bottom - rect.top) + + openedSubmenuDetails = openedSubmenuDetails.push(Immutable.fromJS({ + y: (rect.top - itemHeight), + template: selectedMenuItem.get('submenu') + })) + + windowActions.setContextMenuDetail(this.props.contextMenuDetail.set('openedSubmenuDetails', openedSubmenuDetails)) + } + break + + case keyCodes.UP: + case keyCodes.DOWN: + if (this.props.contextMenuDetail) { + const nextIndex = wrappingClamp( + currentIndex + (e.which === keyCodes.UP ? -1 : 1), + 0, + this.maxIndex(selectedTemplate)) + + const newIndices = selectedIndex.slice() + newIndices[selectedIndex.length - 1] = nextIndex + windowActions.setContextMenuSelectedIndex(newIndices) + } + break + } + } + + onKeyUp (e) { + e.preventDefault() + } + onClick () { windowActions.resetMenuState() } + + getTemplateItemsOnly (template) { + return template.filter((element) => { + if (element.get('type') === separatorMenuItem.type) return false + if (element.has('visible')) return element.get('visible') + return true + }) + } + + getMenuByIndex (selectedIndex, parentItem, currentDepth) { + parentItem = this.getTemplateItemsOnly(parentItem) + if (!currentDepth) currentDepth = 0 + + const selectedIndices = selectedIndex.slice(1) + if (selectedIndices.length === 0) return parentItem + + const submenuIndex = selectedIndex[0] + const childItem = parentItem.get(submenuIndex) + + if (childItem && childItem.get('submenu')) { + return this.getMenuByIndex(selectedIndices, childItem.get('submenu'), currentDepth + 1) + } + + return parentItem + } + + getContextMenuItemBounds () { + const selected = document.querySelectorAll('.contextMenuItem.selectedByKeyboard') + if (selected.length > 0) { + return selected.item(selected.length - 1).getBoundingClientRect() + } + return null + } + + /** + * Upper bound for the active / focused menu. + */ + maxIndex (template) { + return template.filter((element) => { + if (element.get('type') === separatorMenuItem.type) return false + if (element.has('visible')) return element.get('visible') + return true + }).size - 1 + } + get openedSubmenuDetails () { return this.props.contextMenuDetail.get('openedSubmenuDetails') || new Immutable.List() } + + get hasSubmenuSelection () { + return this.props.selectedIndex.length > 1 + } + render () { + const selectedIndex = (this.props.selectedIndex === null) ? [0] : this.props.selectedIndex const styles = {} if (this.props.contextMenuDetail.get('left') !== undefined) { styles.left = this.props.contextMenuDetail.get('left') @@ -290,7 +427,7 @@ class ContextMenu extends ImmutableComponent { submenuIndex={0} lastZoomPercentage={this.props.lastZoomPercentage} template={this.props.contextMenuDetail.get('template')} - selectedIndex={this.props.selectedIndex ? this.props.selectedIndex[0] : null} /> + selectedIndex={selectedIndex[0]} /> { this.openedSubmenuDetails.map((openedSubmenuDetail, i) => ) } diff --git a/js/components/main.js b/js/components/main.js index 22c9af543d8..2b21d4782ce 100644 --- a/js/components/main.js +++ b/js/components/main.js @@ -156,10 +156,10 @@ class Main extends ImmutableComponent { windowActions.toggleMenubarVisible(null) } else { if (customTitlebar.menubarSelectedIndex) { - windowActions.setSubmenuSelectedIndex() + windowActions.setMenuBarSelectedIndex() windowActions.setContextMenuDetail() } else { - windowActions.setSubmenuSelectedIndex([0]) + windowActions.setMenuBarSelectedIndex(0) } } } @@ -172,7 +172,7 @@ class Main extends ImmutableComponent { } if (customTitlebar.menubarSelectedIndex) { e.preventDefault() - windowActions.setSubmenuSelectedIndex() + windowActions.setMenuBarSelectedIndex() windowActions.setContextMenuDetail() } break @@ -825,15 +825,15 @@ class Main extends ImmutableComponent { const customTitlebarEnabled = isWindows const captionButtonsVisible = customTitlebarEnabled const menubarVisible = customTitlebarEnabled && (!getSetting(settings.AUTO_HIDE_MENU) || this.props.windowState.getIn(['ui', 'menubar', 'isVisible'])) - const selectedIndex = this.props.windowState.getIn(['ui', 'menubar', 'selectedIndex']) + const selectedIndex = this.props.windowState.getIn(['ui', 'contextMenu', 'selectedIndex']) return { enabled: customTitlebarEnabled, captionButtonsVisible: captionButtonsVisible, menubarVisible: menubarVisible, menubarTemplate: menubarVisible ? this.props.appState.getIn(['menu', 'template']) : null, - menubarSelectedIndex: selectedIndex, - contextMenuSelectedIndex: typeof selectedIndex === 'object' && Array.isArray(selectedIndex) && selectedIndex.length > 1 - ? selectedIndex.slice(1) + menubarSelectedIndex: this.props.windowState.getIn(['ui', 'menubar', 'selectedIndex']), + contextMenuSelectedIndex: typeof selectedIndex === 'object' && Array.isArray(selectedIndex) && selectedIndex.length > 0 + ? selectedIndex : null, lastFocusedSelector: this.props.windowState.getIn(['ui', 'menubar', 'lastFocusedSelector']), isMaximized: currentWindow.isMaximized() || currentWindow.isFullScreen() @@ -938,6 +938,7 @@ class Main extends ImmutableComponent { diff --git a/js/constants/windowConstants.js b/js/constants/windowConstants.js index ffc52d4d922..c34cd3ff944 100644 --- a/js/constants/windowConstants.js +++ b/js/constants/windowConstants.js @@ -47,6 +47,7 @@ const windowConstants = { WINDOW_SET_FIND_DETAIL: _, WINDOW_SET_BOOKMARK_DETAIL: _, // If set, also indicates that add/edit is shown WINDOW_SET_CONTEXT_MENU_DETAIL: _, // If set, also indicates that the context menu is shown + WINDOW_SET_CONTEXT_MENU_SELECTED_INDEX: _, WINDOW_SET_POPUP_WINDOW_DETAIL: _, // If set, also indicates that the popup window is shown WINDOW_HIDE_BOOKMARK_HANGER: _, WINDOW_SET_AUDIO_MUTED: _, @@ -79,7 +80,7 @@ const windowConstants = { WINDOW_TOGGLE_MENUBAR_VISIBLE: _, WINDOW_CLICK_MENUBAR_SUBMENU: _, WINDOW_RESET_MENU_STATE: _, - WINDOW_SET_SUBMENU_SELECTED_INDEX: _, + WINDOW_SET_MENUBAR_SELECTED_INDEX: _, WINDOW_SET_LAST_FOCUSED_SELECTOR: _, WINDOW_GOT_RESPONSE_DETAILS: _, WINDOW_SET_BOOKMARKS_TOOLBAR_SELECTED_FOLDER_ID: _, diff --git a/js/stores/windowStore.js b/js/stores/windowStore.js index 861a1e5bc9b..604dd8434cd 100644 --- a/js/stores/windowStore.js +++ b/js/stores/windowStore.js @@ -697,7 +697,7 @@ const doAction = (action) => { : !windowState.getIn(['ui', 'menubar', 'isVisible']) // Clear selection when menu is shown if (newVisibleStatus) { - doAction({ actionType: windowConstants.WINDOW_SET_SUBMENU_SELECTED_INDEX, index: [0] }) + doAction({ actionType: windowConstants.WINDOW_SET_MENUBAR_SELECTED_INDEX, index: 0 }) } windowState = windowState.setIn(['ui', 'menubar', 'isVisible'], newVisibleStatus) } @@ -716,14 +716,18 @@ const doAction = (action) => { } else { doAction({actionType: windowConstants.WINDOW_SET_CONTEXT_MENU_DETAIL}) } - doAction({actionType: windowConstants.WINDOW_SET_SUBMENU_SELECTED_INDEX}) + doAction({actionType: windowConstants.WINDOW_SET_MENUBAR_SELECTED_INDEX}) + doAction({actionType: windowConstants.WINDOW_SET_CONTEXT_MENU_SELECTED_INDEX}) doAction({actionType: windowConstants.WINDOW_SET_BOOKMARKS_TOOLBAR_SELECTED_FOLDER_ID}) break - case windowConstants.WINDOW_SET_SUBMENU_SELECTED_INDEX: - windowState = windowState.setIn(['ui', 'menubar', 'selectedIndex'], - Array.isArray(action.index) - ? action.index - : null) + case windowConstants.WINDOW_SET_MENUBAR_SELECTED_INDEX: + windowState = windowState.setIn(['ui', 'menubar', 'selectedIndex'], action.index) + break + case windowConstants.WINDOW_SET_CONTEXT_MENU_SELECTED_INDEX: + windowState = windowState.setIn(['ui', 'contextMenu', 'selectedIndex'], + Array.isArray(action.index) + ? action.index + : null) break case windowConstants.WINDOW_SET_LAST_FOCUSED_SELECTOR: windowState = windowState.setIn(['ui', 'menubar', 'lastFocusedSelector'], action.selector) diff --git a/test/components/contextMenuTest.js b/test/components/contextMenuTest.js new file mode 100644 index 00000000000..d644b6e49a3 --- /dev/null +++ b/test/components/contextMenuTest.js @@ -0,0 +1,128 @@ +/* global describe, it, before */ + +const Brave = require('../lib/brave') +const {urlInput} = require('../lib/selectors') + +describe('ContextMenu', function () { + function * setup (client) { + yield client + .waitForUrl(Brave.newTabUrl) + .waitForBrowserWindow() + .waitForEnabled(urlInput) + } + + describe('navigation', function () { + Brave.beforeAll(this) + + before(function *() { + yield setup(this.app.client) + this.formfill = Brave.server.url('formfill.html') + this.input = '[name="01___title"]' + this.values = ['Value 1', 'Value 2', 'Value 3', 'Value 4'] + + yield this.app.client + .tabByIndex(0) + .loadUrl(this.formfill) + .waitForVisible('
') + + for (let i = 0; i < this.values.length; i++) { + yield this.app.client + .click(this.input) + .keys(this.values[i]) + .click('#submit') + .waitForVisible('') + } + }) + + it('check if context menu exists', function *() { + yield this.app.client + .tabByIndex(0) + .loadUrl(this.formfill) + .waitForVisible('') + .click(this.input) + .click(this.input) + .windowByUrl(Brave.browserWindowUrl) + .waitForVisible('.contextMenuItemText') + }) + + it('check if enter is prevented', function *() { + yield this.app.client + .tabByIndex(0) + .loadUrl(this.formfill) + .waitForVisible('') + .click('[name="02frstname"]') + .keys('Test value') + .click(this.input) + .click(this.input) + .windowByUrl(Brave.browserWindowUrl) + .waitForVisible('.contextMenu') + .keys('\uE015') + .pause(10) + .keys('\uE015') + .pause(10) + .keys('\uE007') + .pause(10) + .tabByUrl(this.formfill) + .getValue('[name="02frstname"]').should.eventually.be.equal('Test value') + .getValue(this.input).should.eventually.be.equal(this.values[2]) + }) + + it('click on item', function *() { + yield this.app.client + .tabByIndex(0) + .loadUrl(this.formfill) + .waitForVisible('') + .click(this.input) + .click(this.input) + .windowByUrl(Brave.browserWindowUrl) + .waitForVisible('.contextMenu') + .click('.contextMenuItem') + .tabByUrl(this.formfill) + .getValue(this.input).should.eventually.be.equal(this.values[0]) + }) + + it('select item via click and keys', function *() { + yield this.app.client + .tabByIndex(0) + .loadUrl(this.formfill) + .waitForVisible('') + .click(this.input) + .click(this.input) + .windowByUrl(Brave.browserWindowUrl) + .waitForVisible('.contextMenu') + .keys('\uE015') + .pause(10) + .keys('\uE015') + .pause(10) + .keys('\uE007') + .pause(10) + .tabByUrl(this.formfill) + .getValue(this.input).should.eventually.be.equal(this.values[2]) + }) + + it('select item via keys only', function *() { + yield this.app.client + .tabByIndex(0) + .loadUrl(this.formfill) + .waitForVisible('') + .click(this.input) + .windowByUrl(Brave.browserWindowUrl) + .keys('\uE015') + .waitForVisible('.contextMenu') + .keys('\uE015') + .pause(10) + .keys('\uE015') + .pause(10) + .keys('\uE015') + .pause(10) + .keys('\uE015') + .pause(10) + .keys('\uE013') + .pause(10) + .keys('\uE007') + .pause(10) + .tabByUrl(this.formfill) + .getValue(this.input).should.eventually.be.equal(this.values[3]) + }) + }) +})