| // Copyright 2016 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Include test fixture. |
| GEN_INCLUDE(['panel_test_base.js']); |
| |
| /** |
| * Test fixture for Panel. |
| */ |
| ChromeVoxPanelTest = class extends ChromeVoxPanelTestBase { |
| /** @override */ |
| async setUpDeferred() { |
| await super.setUpDeferred(); |
| |
| // Alphabetical based on file path. |
| await importModule( |
| 'ChromeVoxState', '/chromevox/background/chromevox_state.js'); |
| await importModule( |
| 'CommandHandlerInterface', |
| '/chromevox/background/command_handler_interface.js'); |
| await importModule( |
| 'EventSourceState', '/chromevox/background/event_source.js'); |
| await importModule( |
| 'EventSourceType', '/chromevox/common/event_source_type.js'); |
| await importModule( |
| 'LocaleOutputHelper', '/chromevox/common/locale_output_helper.js'); |
| await importModule( |
| ['PanelCommand', 'PanelCommandType'], |
| '/chromevox/common/panel_command.js'); |
| await importModule('CursorRange', '/common/cursors/range.js'); |
| await importModule('LocalStorage', '/common/local_storage.js'); |
| } |
| |
| fireMockEvent(key) { |
| return function() { |
| const obj = {}; |
| obj.preventDefault = function() {}; |
| obj.stopPropagation = function() {}; |
| obj.key = key; |
| this.getPanel().instance_.onKeyDown_(obj); |
| }.bind(this); |
| } |
| |
| fireMockQuery(query) { |
| return function() { |
| const evt = {}; |
| evt.target = {}; |
| evt.target.value = query; |
| this.getPanel().instance_.onSearchBarQuery_(evt); |
| }.bind(this); |
| } |
| |
| async waitForMenu(menuMsg) { |
| // Menu and menu item updates occur in a different js context, so tests need |
| // to wait until an update has been made. Swap in our hook, wait, then |
| // restore after. |
| const makeAssertions = () => { |
| const menu = this.getPanel().instance_.activeMenu_; |
| assertEquals(menuMsg, menu.menuMsg); |
| }; |
| |
| return new Promise(resolve => { |
| const Panel = this.getPanel(); |
| const original = Panel.instance_.activateMenu_.bind(Panel.instance_); |
| Panel.instance_.activateMenu_ = (menu, activateFirstItem) => { |
| original(menu, activateFirstItem); |
| makeAssertions(); |
| Panel.instance_.activateMenu_ = original; |
| resolve(); |
| }; |
| }); |
| } |
| |
| assertActiveMenuItem(menuMsg, menuItemTitle, opt_menuItemShortcut) { |
| const menu = this.getPanel().instance_.activeMenu_; |
| const menuItem = menu.items_[menu.activeIndex_]; |
| assertEquals(menuMsg, menu.menuMsg); |
| assertEquals(menuItemTitle, menuItem.menuItemTitle); |
| if (opt_menuItemShortcut) { |
| assertEquals(opt_menuItemShortcut, menuItem.menuItemShortcut); |
| } |
| } |
| |
| assertActiveSearchMenuItem(menuItemTitle) { |
| const searchMenu = this.getPanel().instance_.searchMenu_; |
| const activeIndex = searchMenu.activeIndex_; |
| const activeItem = searchMenu.items_[activeIndex]; |
| assertEquals(menuItemTitle, activeItem.menuItemTitle); |
| } |
| |
| enableTouchMode() { |
| EventSourceState.set(EventSourceType.TOUCH_GESTURE); |
| } |
| |
| isMenuTitleMessage(menuTitleMessage) { |
| const menu = this.getPanel().instance_.activeMenu_; |
| return menuTitleMessage === menu.menuMsg; |
| } |
| |
| get linksDoc() { |
| return ` |
| <p>start</p> |
| <a href="#">apple</a> |
| <a href="#">grape</a> |
| <a href="#">banana</a> |
| `; |
| } |
| |
| get internationalButtonDoc() { |
| return ` |
| <button lang="en">Test</button> |
| <button lang="es">Prueba</button> |
| `; |
| } |
| }; |
| |
| AX_TEST_F('ChromeVoxPanelTest', 'ActivateMenu', async function() { |
| await this.runWithLoadedTree(this.linksDoc); |
| new PanelCommand(PanelCommandType.OPEN_MENUS).send(); |
| await this.waitForMenu('panel_search_menu'); |
| this.fireMockEvent('ArrowRight')(); |
| this.assertActiveMenuItem('panel_menu_jump', 'Go To Beginning Of Table'); |
| this.fireMockEvent('ArrowRight')(); |
| this.assertActiveMenuItem( |
| 'panel_menu_speech', 'Announce Current Battery Status'); |
| }); |
| |
| // TODO(https://crbug.com/1299765): Re-enable once flaky timeouts are fixed. |
| AX_TEST_F('ChromeVoxPanelTest', 'DISABLED_LinkMenu', async function() { |
| await this.runWithLoadedTree(this.linksDoc); |
| CommandHandlerInterface.instance.onCommand('showLinksList'); |
| await this.waitForMenu('role_link'); |
| this.fireMockEvent('ArrowLeft')(); |
| this.assertActiveMenuItem('role_landmark', 'No items'); |
| this.fireMockEvent('ArrowRight')(); |
| this.assertActiveMenuItem('role_link', 'apple Internal link'); |
| this.fireMockEvent('ArrowUp')(); |
| this.assertActiveMenuItem('role_link', 'banana Internal link'); |
| }); |
| |
| AX_TEST_F('ChromeVoxPanelTest', 'FormControlsMenu', async function() { |
| await this.runWithLoadedTree(`<button>Cancel</button><button>OK</button>`); |
| CommandHandlerInterface.instance.onCommand('showFormsList'); |
| await this.waitForMenu('panel_menu_form_controls'); |
| this.fireMockEvent('ArrowDown')(); |
| this.assertActiveMenuItem('panel_menu_form_controls', 'OK Button'); |
| this.fireMockEvent('ArrowUp')(); |
| this.assertActiveMenuItem('panel_menu_form_controls', 'Cancel Button'); |
| }); |
| |
| |
| // TODO(https://crbug.com/1333375): Flaky on MSAN and ASAN builders. |
| GEN('#if defined(MEMORY_SANITIZER) || defined(ADDRESS_SANITIZER)'); |
| GEN('#define MAYBE_SearchMenu DISABLED_SearchMenu'); |
| GEN('#else'); |
| GEN('#define MAYBE_SearchMenu SearchMenu'); |
| GEN('#endif'); |
| |
| AX_TEST_F('ChromeVoxPanelTest', 'MAYBE_SearchMenu', async function() { |
| const mockFeedback = this.createMockFeedback(); |
| await this.runWithLoadedTree(this.linksDoc); |
| new PanelCommand(PanelCommandType.OPEN_MENUS).send(); |
| await this.waitForMenu('panel_search_menu'); |
| await mockFeedback |
| .expectSpeech('Search the menus', /Type to search the menus/) |
| .call(() => { |
| this.fireMockQuery('jump')(); |
| this.assertActiveSearchMenuItem('Jump To Details'); |
| }) |
| .expectSpeech(/Jump/, 'Menu item', /[0-9]+ of [0-9]+/) |
| .call(() => { |
| this.fireMockEvent('ArrowDown')(); |
| this.assertActiveSearchMenuItem('Jump To The Bottom Of The Page'); |
| }) |
| .expectSpeech(/Jump/, 'Menu item', /[0-9]+ of [0-9]+/) |
| .call(() => { |
| this.fireMockEvent('ArrowDown')(); |
| this.assertActiveSearchMenuItem('Jump To The Top Of The Page'); |
| }) |
| .expectSpeech(/Jump/, 'Menu item', /[0-9]+ of [0-9]+/) |
| .call(() => { |
| this.fireMockEvent('ArrowDown')(); |
| this.assertActiveSearchMenuItem('Jump To Details'); |
| }) |
| .expectSpeech(/Jump/, 'Menu item', /[0-9]+ of [0-9]+/) |
| .replay(); |
| }); |
| |
| // TODO(crbug.com/1088438): flaky crashes. |
| AX_TEST_F('ChromeVoxPanelTest', 'DISABLED_Gestures', async function() { |
| const doGestureAsync = async gesture => { |
| doGesture(gesture)(); |
| }; |
| await this.runWithLoadedTree(`<button>Cancel</button><button>OK</button>`); |
| doGestureAsync(Gesture.TAP4); |
| await this.waitForMenu('panel_search_menu'); |
| // GestureCommandHandler behaves in special ways only with range over |
| // the panel. Fake this out by setting range there. |
| const desktop = root.parent.root; |
| const panelNode = desktop.find( |
| {role: 'rootWebArea', attributes: {name: 'ChromeVox Panel'}}); |
| ChromeVoxState.instance.setCurrentRange(CursorRange.fromNode(panelNode)); |
| |
| doGestureAsync(Gesture.SWIPE_RIGHT1); |
| await this.waitForMenu('panel_menu_jump'); |
| |
| doGestureAsync(Gesture.SWIPE_RIGHT1); |
| await this.waitForMenu('panel_menu_speech'); |
| |
| doGestureAsync(Gesture.SWIPE_LEFT1); |
| await this.waitForMenu('panel_menu_jump'); |
| }); |
| |
| AX_TEST_F( |
| 'ChromeVoxPanelTest', 'InternationalFormControlsMenu', async function() { |
| await this.runWithLoadedTree(this.internationalButtonDoc); |
| // Turn on language switching and set available voice list. |
| LocalStorage.set('languageSwitching', true); |
| LocaleOutputHelper.instance.availableVoices_ = |
| [{'lang': 'en-US'}, {'lang': 'es-ES'}]; |
| CommandHandlerInterface.instance.onCommand('showFormsList'); |
| await this.waitForMenu('panel_menu_form_controls'); |
| this.fireMockEvent('ArrowDown')(); |
| this.assertActiveMenuItem( |
| 'panel_menu_form_controls', 'espaƱol: Prueba Button'); |
| this.fireMockEvent('ArrowUp')(); |
| this.assertActiveMenuItem('panel_menu_form_controls', 'Test Button'); |
| }); |
| |
| AX_TEST_F('ChromeVoxPanelTest', 'ActionsMenu', async function() { |
| await this.runWithLoadedTree(this.linksDoc); |
| CommandHandlerInterface.instance.onCommand('showActionsMenu'); |
| await this.waitForMenu('panel_menu_actions'); |
| this.fireMockEvent('ArrowDown')(); |
| this.assertActiveMenuItem('panel_menu_actions', 'Start Or End Selection'); |
| this.fireMockEvent('ArrowUp')(); |
| this.assertActiveMenuItem('panel_menu_actions', 'Click On Current Item'); |
| }); |
| |
| AX_TEST_F('ChromeVoxPanelTest', 'ActionsMenuLongClick', async function() { |
| await this.runWithLoadedTree(this.linksDoc); |
| // Get the node that will be checked for actions. |
| const activeNode = ChromeVoxState.instance.currentRange.start.node; |
| // Override the standard actions to have long click. |
| Object.defineProperty(activeNode, 'standardActions', { |
| value: ['longClick'], |
| writable: true, |
| }); |
| CommandHandlerInterface.instance.onCommand('showActionsMenu'); |
| await this.waitForMenu('panel_menu_actions'); |
| // Go down three times |
| this.fireMockEvent('ArrowUp')(); |
| this.assertActiveMenuItem('panel_menu_actions', 'Long click on current item'); |
| this.fireMockEvent('ArrowDown')(); |
| this.assertActiveMenuItem('panel_menu_actions', 'Click On Current Item'); |
| }); |
| |
| AX_TEST_F( |
| 'ChromeVoxPanelTest', 'ShortcutsAreInternationalized', async function() { |
| await this.runWithLoadedTree(this.linksDoc); |
| new PanelCommand(PanelCommandType.OPEN_MENUS).send(); |
| await this.waitForMenu('panel_search_menu'); |
| this.fireMockEvent('ArrowRight')(); |
| this.assertActiveMenuItem( |
| 'panel_menu_jump', 'Go To Beginning Of Table', |
| 'Search+Alt+Shift+ArrowLeft'); |
| this.fireMockEvent('ArrowRight')(); |
| this.assertActiveMenuItem( |
| 'panel_menu_speech', 'Announce Current Battery Status', |
| 'Search+O, then B'); |
| // Skip the tabs menu. |
| this.fireMockEvent('ArrowRight')(); |
| this.fireMockEvent('ArrowRight')(); |
| this.assertActiveMenuItem( |
| 'panel_menu_chromevox', 'Open keyboard shortcuts menu', 'Ctrl+Alt+/'); |
| }); |
| |
| // Ensure 'Touch Gestures' is not in the panel menus by default. |
| AX_TEST_F( |
| 'ChromeVoxPanelTest', 'TouchGesturesMenuNotAvailableWhenNotInTouchMode', |
| async function() { |
| await this.runWithLoadedTree(this.linksDoc); |
| new PanelCommand(PanelCommandType.OPEN_MENUS).send(); |
| await this.waitForMenu('panel_search_menu'); |
| do { |
| this.fireMockEvent('ArrowRight')(); |
| assertFalse(this.isMenuTitleMessage('panel_menu_touchgestures')); |
| } while (!this.isMenuTitleMessage('panel_search_menu')); |
| }); |
| |
| // Ensure 'Touch Gesture' is in the panel menus when touch mode is enabled. |
| AX_TEST_F( |
| 'ChromeVoxPanelTest', 'TouchGesturesMenuAvailableWhenInTouchMode', |
| async function() { |
| await this.runWithLoadedTree(this.linksDoc); |
| this.enableTouchMode(); |
| new PanelCommand(PanelCommandType.OPEN_MENUS).send(); |
| await this.waitForMenu('panel_search_menu'); |
| |
| // Look for Touch Gestures menu, fail if getting back to start. |
| do { |
| this.fireMockEvent('ArrowRight')(); |
| assertFalse(this.isMenuTitleMessage('panel_search_menu')); |
| } while (!this.isMenuTitleMessage('panel_menu_touchgestures')); |
| |
| this.assertActiveMenuItem( |
| 'panel_menu_touchgestures', 'Click on current item'); |
| }); |
| |
| // Ensure 'Perform default action' in the actions tab invokes a click event. |
| AX_TEST_F('ChromeVoxPanelTest', 'PerformDoDefaultAction', async function() { |
| const rootNode = await this.runWithLoadedTree(`<button>OK</button>`); |
| const button = rootNode.find({role: RoleType.BUTTON}); |
| await this.waitForEvent(button, chrome.automation.EventType.FOCUS); |
| CommandHandlerInterface.instance.onCommand('showActionsMenu'); |
| await this.waitForMenu('panel_menu_actions'); |
| this.fireMockEvent('ArrowDown')(); |
| this.assertActiveMenuItem('panel_menu_actions', 'Start Or End Selection'); |
| this.fireMockEvent('ArrowDown')(); |
| this.fireMockEvent('ArrowDown')(); |
| this.assertActiveMenuItem('panel_menu_actions', 'Perform default action'); |
| this.fireMockEvent('Enter')(); |
| await this.waitForEvent(button, chrome.automation.EventType.CLICKED); |
| }); |