/* eslint-env qunit */
import DomData from '../../src/js/utils/dom-data';
import MenuButton from '../../src/js/menu/menu-button.js';
import Menu from '../../src/js/menu/menu.js';
import CaptionSettingsMenuItem from '../../src/js/control-bar/text-track-controls/caption-settings-menu-item';
import MenuItem from '../../src/js/menu/menu-item.js';
import TestHelpers from './test-helpers.js';
import * as Events from '../../src/js/utils/events.js';
import sinon from 'sinon';
import window from 'global/window';
import document from 'global/document';

QUnit.module('MenuButton');

QUnit.test('should not throw an error when there is no children', function(assert) {
  assert.expect(0);
  const player = TestHelpers.makePlayer();

  const menuButton = new MenuButton(player);
  const el = menuButton.el();

  try {
    Events.trigger(el, 'click');
  } catch (error) {
    assert.ok(!error, 'click should not throw anything');
  }

  player.dispose();
  menuButton.dispose();
});

QUnit.test('should place title list item into ul', function(assert) {
  const player = TestHelpers.makePlayer();

  const menuButton = new MenuButton(player, {
    title: 'testTitle'
  });

  const menuContentElement = menuButton.el().getElementsByTagName('UL')[0];
  const titleElement = menuContentElement.children[0];

  assert.ok(titleElement.innerHTML === 'TestTitle', 'title element placed in ul');

  menuButton.dispose();
  player.dispose();
});

QUnit.test('should not include menu title in hide threshold', function(assert) {
  const player = TestHelpers.makePlayer();

  const menuButton = new MenuButton(player, {
    title: 'testTitle'
  });

  assert.strictEqual(menuButton.hideThreshold_, 0, 'title should not increment hideThreshold_');

  menuButton.createItems = () => [new MenuItem(player, { label: 'menu-item1' })];
  menuButton.update();

  assert.strictEqual(menuButton.hasClass('vjs-hidden'), false, 'menu button with single (non-title) item is not hidden');

  menuButton.dispose();
  player.dispose();
});

QUnit.test('clicking should display the menu', function(assert) {
  assert.expect(6);

  const player = TestHelpers.makePlayer();

  // Make sure there's some content in the menu, even if it's just a title!
  const menuButton = new MenuButton(player, {
    title: 'testTitle'
  });
  const el = menuButton.menuButton_.el();

  assert.ok(menuButton.menu !== undefined, 'menu is created');

  assert.equal(menuButton.menu.hasClass('vjs-lock-showing'), false, 'menu defaults to hidden');

  Events.trigger(el, 'click');

  assert.equal(menuButton.menu.hasClass('vjs-lock-showing'), true, 'clicking on the menu button shows the menu');

  Events.trigger(el, 'click');

  assert.equal(menuButton.menu.hasClass('vjs-lock-showing'), false, 'clicking again on the menu button hides the menu');

  menuButton.disable();

  Events.trigger(el, 'click');

  assert.equal(menuButton.menu.hasClass('vjs-lock-showing'), false, 'disable() prevents clicking from showing the menu');

  menuButton.enable();

  Events.trigger(el, 'click');

  assert.equal(menuButton.menu.hasClass('vjs-lock-showing'), true, 'enable() allows clicking to show the menu');

  menuButton.dispose();
  player.dispose();
});

QUnit.test('should keep all the added menu items', function(assert) {
  const player = TestHelpers.makePlayer();

  const menuItems = [];
  const menuItem1 = new MenuItem(player, { label: 'menu-item1' });
  const menuItem2 = new MenuItem(player, { label: 'menu-item2' });

  const oldCreateItems = MenuButton.prototype.createItems;

  MenuButton.prototype.createItems = function() {
    return menuItems;
  };

  const menuButton = new MenuButton(player, {});

  menuItems.push(menuItem1);
  menuButton.update();

  assert.strictEqual(menuButton.children()[1].children().length, 1, 'the children number of the menu is 1 ');
  assert.strictEqual(menuButton.children()[1].children()[0], menuItem1, 'the first child of the menu is `menuItem1`');
  assert.ok(menuButton.el().contains(menuItem1.el()), 'the menu button contains the DOM element of `menuItem1`');

  menuItems.push(menuItem2);
  menuButton.update();

  assert.strictEqual(menuButton.children()[1].children().length, 2, 'the children number of the menu is 2 after second update');
  assert.strictEqual(menuButton.children()[1].children()[0], menuItem1, 'the first child of the menu is `menuItem1` after second update');
  assert.strictEqual(menuButton.children()[1].children()[1], menuItem2, 'the second child of the menu is `menuItem2` after second update');
  assert.ok(menuButton.el().contains(menuItem1.el()), 'the menu button contains the DOM element of `menuItem1` after second update');
  assert.ok(menuButton.el().contains(menuItem2.el()), 'the menu button contains the DOM element of `menuItem2` after second update');

  menuButton.dispose();
  menuItem1.dispose();
  menuItem2.dispose();
  player.dispose();

  MenuButton.prototype.createItems = oldCreateItems;
});

QUnit.test('should add or remove role menu for accessibility purpose', function(assert) {
  const player = TestHelpers.makePlayer();
  const menuButton = new MenuButton(player);

  menuButton.createItems = () => [];
  menuButton.update();
  assert.equal(menuButton.menu.contentEl_.hasAttribute('role'), false, 'the menu does not have a role attribute when it contains no menu items');

  menuButton.createItems = () => [new MenuItem(player, { label: 'menu-item' })];
  menuButton.update();
  assert.equal(menuButton.menu.contentEl_.hasAttribute('role'), true, 'the menu has a role attribute when it contains menu items');
  assert.strictEqual(menuButton.menu.contentEl_.getAttribute('role'), 'menu', 'the menu role is `menu`');

  menuButton.dispose();
  player.dispose();
});

QUnit.test('setIcon should apply a child to the Button component', function(assert) {
  // Stub a successful parsing of the SVG sprite.
  sinon.stub(window.DOMParser.prototype, 'parseFromString').returns({
    querySelector: () => false,
    documentElement: document.createElement('span')
  });

  const player = TestHelpers.makePlayer({experimentalSvgIcons: true});
  const menuButton = new MenuButton(player);

  menuButton.createItems = () => [];
  menuButton.update();

  menuButton.setIcon('test');

  const buttonEl = menuButton.menuButton_.el_;
  const spanEl = buttonEl.getElementsByClassName('vjs-svg-icon')[0];
  const svgEl = spanEl.childNodes[0];
  const useEl = svgEl.childNodes[0];

  assert.equal(useEl.getAttribute('href'), '#vjs-icon-test', 'use should have an href set with the correct icon url');

  window.DOMParser.prototype.parseFromString.restore();
  menuButton.dispose();
  player.dispose();
});

QUnit.test('should remove old event listeners when the menu item adds to the new menu', function(assert) {
  const player = TestHelpers.makePlayer();
  const menuButton = new MenuButton(player, {});
  const oldMenu = new Menu(player, { menuButton });
  const newMenu = new Menu(player, { menuButton });

  oldMenu.addItem('MenuItem');

  const menuItem = oldMenu.children()[0];

  assert.ok(menuItem instanceof MenuItem, '`menuItem` should be the instanceof of `MenuItem`');

  /**
   * A reusable collection of assertions.
   */
  function validateMenuEventListeners(watchedMenu) {
    const eventData = DomData.get(menuItem.eventBusEl_);
    // `MenuButton`.`unpressButton` will be called when triggering click event on the menu item.
    const unpressButtonSpy = sinon.spy(menuButton, 'unpressButton');
    // `MenuButton`.`focus` will be called when triggering click event on the menu item.
    const focusSpy = sinon.spy(menuButton, 'focus');

    // `Menu`.`children` will be called when triggering blur event on the menu item.
    const menuChildrenSpy = sinon.spy(watchedMenu, 'children');

    assert.strictEqual(eventData.handlers.blur.length, 1, 'the number of blur listeners is one');

    // The number of click listeners is two because `ClickableComponent`
    // adds the click event listener during the construction and
    // `MenuItem` inherits from `ClickableComponent`.
    assert.strictEqual(eventData.handlers.click.length, 2, 'the number of click listeners is two');

    const clickListenerAddedByMenu = eventData.handlers.click[1];

    assert.strictEqual(
      typeof clickListenerAddedByMenu.calledOnce,
      'undefined',
      'previous click listener wrapped in the spy should be removed'
    );

    const clickListenerSpy = eventData.handlers.click[1] = sinon.spy(clickListenerAddedByMenu);

    TestHelpers.triggerDomEvent(menuItem.el(), 'blur');

    assert.ok(menuChildrenSpy.calledOnce, '`watchedMenu`.`children` has been called');

    TestHelpers.triggerDomEvent(menuItem.el(), 'click');

    assert.ok(clickListenerSpy.calledOnce, 'click event listener should be called');
    assert.strictEqual(clickListenerSpy.getCall(0).args[0].target, menuItem.el(), 'event target should be the `menuItem`');
    assert.ok(unpressButtonSpy.calledOnce, '`menuButton`.`unpressButton` has been called');
    assert.ok(focusSpy.calledOnce, '`menuButton`.`focus` has been called');

    unpressButtonSpy.restore();
    focusSpy.restore();
    menuChildrenSpy.restore();
  }

  validateMenuEventListeners(oldMenu);

  newMenu.addItem(menuItem);
  validateMenuEventListeners(newMenu);

  const focusSpy = sinon.spy(menuButton, 'focus');
  const captionMenuItem = new CaptionSettingsMenuItem(player, {
    kind: 'subtitles'
  });

  newMenu.addItem(captionMenuItem);

  TestHelpers.triggerDomEvent(captionMenuItem.el(), 'click');
  assert.ok(!focusSpy.called, '`menuButton`.`focus` should never be called');

  focusSpy.restore();

  player.dispose();
  newMenu.dispose();
  oldMenu.dispose();
  menuButton.dispose();
});

QUnit.test('Escape should close menu', function(assert) {
  const player = TestHelpers.makePlayer();
  const menuButton = new MenuButton(player, {});
  const unpressButtonSpy = sinon.spy(menuButton, 'unpressButton');

  menuButton.createItems = () => [new MenuItem(player, {})];
  menuButton.update();
  menuButton.handleClick(new window.PointerEvent('click'));
  menuButton.menu.children()[0].el_.dispatchEvent(new window.KeyboardEvent('keydown', {
    key: 'Escape',
    bubbles: true,
    cancelable: true
  }));

  assert.ok(unpressButtonSpy.calledOnce, '`menuButton`.`unpressButton` has been called');

});
