chromium/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_test.js

// 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 */
  testGenCppIncludes() {
    super.testGenCppIncludes();
    GEN(`#include "ui/accessibility/accessibility_features.h"`);
  }

  /** @override */
  async setUpDeferred() {
    await super.setUpDeferred();

    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.menuManager_.onSearchBarQuery(evt);
    }.bind(this);
  }

  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'}});
  ChromeVoxRange.set(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');
      this.fireMockEvent('ArrowRight')();
      this.assertActiveMenuItem(
          'panel_menu_chromevox', 'Enable/Disable Sticky Mode',
          'Search+Search');
    });

// 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;
});