| // 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( |
| 'ChromeVoxRange', '/chromevox/background/chromevox_range.js'); |
| await importModule( |
| 'ChromeVoxState', '/chromevox/background/chromevox_state.js'); |
| await importModule( |
| 'CommandHandlerInterface', |
| '/chromevox/background/command_handler_interface.js'); |
| await importModule('EventSource', '/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('MenuManager', '/chromevox/panel/menu_manager.js'); |
| await importModule('CursorRange', '/common/cursors/range.js'); |
| await importModule('LocalStorage', '/common/local_storage.js'); |
| await importModule( |
| 'SettingsManager', '/chromevox/common/settings_manager.js'); |
| |
| globalThis.Gesture = chrome.accessibilityPrivate.Gesture; |
| globalThis.RoleType = chrome.automation.RoleType; |
| |
| const panel = this.getPanel().instance; |
| const original = panel.exec_.bind(panel); |
| panel.exec_ = (command) => { |
| original(command); |
| this.onPanelCommandCalled(); |
| }; |
| } |
| |
| onPanelCommandCalled() { |
| if (this.resolvePanelCommandPromise) { |
| this.resolvePanelCommandPromise(); |
| } |
| } |
| |
| prepareForPanelCommand() { |
| this.panelCommandPromise = |
| new Promise(resolve => this.resolvePanelCommandPromise = resolve); |
| } |
| |
| waitForPanelCommand() { |
| return this.panelCommandPromise; |
| } |
| |
| 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) { |
| const menuManager = this.getPanel().instance.menuManager_; |
| |
| // Menu and menu item updates occur in a different js context, so tests need |
| // to wait until an update has been made. |
| return new Promise( |
| resolve => |
| this.addCallbackPostMethod(menuManager, 'activateMenu', () => { |
| assertEquals(menuMsg, menuManager.activeMenu_.menuMsg); |
| resolve(); |
| }, () => true)); |
| } |
| |
| assertActiveMenuItem(menuMsg, menuItemTitle, opt_menuItemShortcut) { |
| const menu = this.getPanel().instance.menuManager_.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.menuManager_.searchMenu; |
| const activeIndex = searchMenu.activeIndex_; |
| const activeItem = searchMenu.items_[activeIndex]; |
| assertEquals(menuItemTitle, activeItem.menuItemTitle); |
| } |
| |
| enableTouchMode() { |
| EventSource.set(EventSourceType.TOUCH_GESTURE); |
| } |
| |
| isMenuTitleMessage(menuTitleMessage) { |
| const menu = this.getPanel().instance.menuManager_.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. |
| SettingsManager.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 = ChromeVoxRange.current.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); |
| }); |
| |
| AX_TEST_F('ChromeVoxPanelTest', 'PanVirtualBrailleDisplay', async function() { |
| await this.runWithLoadedTree(this.linksDoc); |
| |
| this.prepareForPanelCommand(); |
| CommandHandlerInterface.instance.onCommand('toggleBrailleCaptions'); |
| this.waitForPanelCommand(); |
| |
| // Locate the buttons to pan left and pan right in the display. |
| const panelDocument = this.getPanelWindow().document; |
| const panLeftButton = panelDocument.getElementById('braille-pan-left'); |
| assertNotNullNorUndefined(panLeftButton); |
| const panRightButton = panelDocument.getElementById('braille-pan-right'); |
| assertNotNullNorUndefined(panRightButton); |
| |
| // Mock out ChromeVox.braille to confirm that the commands are routed from the |
| // panel context to the background context. |
| let panLeft; |
| let panRight; |
| const panLeftDone = new Promise(resolve => panLeft = resolve); |
| const panRightDone = new Promise(resolve => panRight = resolve); |
| ChromeVox.braille = {panLeft, panRight}; |
| |
| panLeftButton.click(); |
| await panLeftDone; |
| |
| panRightButton.click(); |
| await panRightDone; |
| }); |