// Copyright 2016 The Chromium Authors. All rights reserved.
// 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 {
  fireMockEvent(key) {
    return function() {
      const obj = {};
      obj.preventDefault = function() {};
      obj.stopPropagation = function() {};
      obj.key = key;
      this.getPanel().onKeyDown(obj);
    }.bind(this);
  }

  fireMockQuery(query) {
    return function() {
      const evt = {};
      evt.target = {};
      evt.target.value = query;
      this.getPanel().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().activeMenu_;
      assertEquals(menuMsg, menu.menuMsg);
    };

    return new Promise(resolve => {
      const Panel = this.getPanel();
      const original = Panel.activateMenu;
      Panel.activateMenu = (menu, activateFirstItem) => {
        original(menu, activateFirstItem);
        makeAssertions();
        Panel.activateMenu = original;
        resolve();
      };
    });
  }

  assertActiveMenuItem(menuMsg, menuItemTitle, opt_menuItemShortcut) {
    const menu = this.getPanel().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().searchMenu;
    const activeIndex = searchMenu.activeIndex_;
    const activeItem = searchMenu.items_[activeIndex];
    assertEquals(menuItemTitle, activeItem.menuItemTitle);
  }

  isMenuTitleMessage(menuTitleMessage) {
    const menu = this.getPanel().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>
    `;
  }
};

TEST_F('ChromeVoxPanelTest', 'ActivateMenu', function() {
  this.runWithLoadedTree(this.linksDoc, async function(root) {
    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.
TEST_F('ChromeVoxPanelTest', 'DISABLED_LinkMenu', function() {
  this.runWithLoadedTree(this.linksDoc, async function(root) {
    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');
  });
});

TEST_F('ChromeVoxPanelTest', 'FormControlsMenu', function() {
  this.runWithLoadedTree(
      `<button>Cancel</button><button>OK</button>`, async function(root) {
        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');
      });
});

TEST_F('ChromeVoxPanelTest', 'SearchMenu', function() {
  const mockFeedback = this.createMockFeedback();
  this.runWithLoadedTree(this.linksDoc, async function(root) {
    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.
TEST_F('ChromeVoxPanelTest', 'DISABLED_Gestures', function() {
  const doGestureAsync = async (gesture) => {
    doGesture(gesture)();
  };
  this.runWithLoadedTree(
      `<button>Cancel</button><button>OK</button>`, async function(root) {
        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(
            cursors.Range.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');
      });
});

TEST_F('ChromeVoxPanelTest', 'InternationalFormControlsMenu', function() {
  this.runWithLoadedTree(this.internationalButtonDoc, async function(root) {
    // Turn on language switching and set available voice list.
    localStorage['languageSwitching'] = 'true';
    this.getPanelWindow().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');
  });
});

TEST_F('ChromeVoxPanelTest', 'ActionsMenu', function() {
  this.runWithLoadedTree(this.linksDoc, async function(root) {
    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');
  });
});

TEST_F('ChromeVoxPanelTest', 'ShortcutsAreInternationalized', function() {
  this.runWithLoadedTree(this.linksDoc, async function(root) {
    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.
TEST_F(
    'ChromeVoxPanelTest', 'TouchGesturesMenuNotAvailableWhenNotInTouchMode',
    function() {
      this.runWithLoadedTree(this.linksDoc, async function(root) {
        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.
TEST_F(
    'ChromeVoxPanelTest', 'TouchGesturesMenuAvailableWhenInTouchMode',
    function() {
      this.runWithLoadedTree(this.linksDoc, async function(root) {
        this.getPanel().setTouchGestureSourceForTesting();
        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');
      });
    });
