// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

goog.provide('goog.ui.PopupMenuTest');
goog.setTestOnly('goog.ui.PopupMenuTest');

goog.require('goog.dom');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventType');
goog.require('goog.math.Box');
goog.require('goog.math.Coordinate');
goog.require('goog.positioning.Corner');
goog.require('goog.style');
goog.require('goog.testing.jsunit');
goog.require('goog.ui.Menu');
goog.require('goog.ui.MenuItem');
goog.require('goog.ui.PopupMenu');

var anchor;

// Event handler
var handler;
var showPopup;
var beforeShowPopupCalled;
var popup;

function setUp() {
  anchor = goog.dom.getElement('popup-anchor');
  handler = new goog.events.EventHandler();
  popup = new goog.ui.PopupMenu();
  popup.render();
}

function tearDown() {
  handler.dispose();
  popup.dispose();
}


/**
 * Asserts properties of {@code target} matches the expected value.
 *
 * @param {Object} target The target specifying how the popup menu should be
 *     attached to an anchor.
 * @param {Element} expectedElement The expected anchoring element.
 * @param {goog.positioning.Corner} expectedTargetCorner The expected value of
 *     the {@code target.targetCorner_} property.
 * @param {goog.positioning.Corner} expectedMenuCorner The expected value of
 *     the {@code target.menuCorner_} property.
 * @param {goog.events.EventType} expectedEventType The expected value of the
 *     {@code target.eventType_} property.
 * @param {goog.math.Box} expectedMargin The expected value of the
 *     {@code target.margin_} property.
 */
function assertTarget(target, expectedElement, expectedTargetCorner,
    expectedMenuCorner, expectedEventType, expectedMargin) {
  var expectedTarget = {
    element_: expectedElement,
    targetCorner_: expectedTargetCorner,
    menuCorner_: expectedMenuCorner,
    eventType_: expectedEventType,
    margin_: expectedMargin
  };

  assertObjectEquals('Target does not match.', expectedTarget, target);
}


/**
 * Test menu receives BEFORE_SHOW event before it's displayed.
 */
function testBeforeShowEvent() {
  var target = popup.createAttachTarget(anchor);
  popup.attach(anchor);

  function beforeShowPopup(e) {
    // Ensure that the element is not yet visible.
    assertFalse('The element should not be shown when BEFORE_SHOW event is ' +
        'being handled',
        goog.style.isElementShown(popup.getElement()));
    // Verify that current anchor is set before dispatching BEFORE_SHOW.
    assertNotNullNorUndefined(popup.getAttachedElement());
    assertEquals('The attached anchor element is incorrect',
        target.element_, popup.getAttachedElement());
    beforeShowPopupCalled = true;
    return showPopup;

  };
  function onShowPopup(e) {
    assertEquals('The attached anchor element is incorrect',
        target.element_, popup.getAttachedElement());
  };

  handler.listen(popup,
                 goog.ui.Menu.EventType.BEFORE_SHOW,
                 beforeShowPopup);
  handler.listen(popup,
                 goog.ui.Menu.EventType.SHOW,
                 onShowPopup);

  beforeShowPopupCalled = false;
  showPopup = false;
  popup.showMenu(target, 0, 0);
  assertTrue('BEFORE_SHOW event handler should be called on #showMenu',
      beforeShowPopupCalled);
  assertFalse('The element should not be shown when BEFORE_SHOW handler ' +
      'returned false',
      goog.style.isElementShown(popup.getElement()));

  beforeShowPopupCalled = false;
  showPopup = true;
  popup.showMenu(target, 0, 0);
  assertTrue('The element should be shown when BEFORE_SHOW handler ' +
      'returned true',
      goog.style.isElementShown(popup.getElement()));
}


/**
 * Test the behavior of {@link PopupMenu.isAttachTarget}.
 */
function testIsAttachTarget() {
  // Before 'attach' is called.
  assertFalse('Menu should not be attached to the element',
      popup.isAttachTarget(anchor));

  popup.attach(anchor);
  assertTrue('Menu should be attached to the anchor',
      popup.isAttachTarget(anchor));

  popup.detach(anchor);
  assertFalse('Menu is expected to be detached from the element',
      popup.isAttachTarget(anchor));
}


/**
 * Tests the behavior of {@link PopupMenu.createAttachTarget}.
 */
function testCreateAttachTarget() {
  // Randomly picking parameters.
  var targetCorner = goog.positioning.Corner.TOP_END;
  var menuCorner = goog.positioning.Corner.BOTTOM_LEFT;
  var contextMenu = false;   // Show menu on mouse down event.
  var margin = new goog.math.Box(0, 10, 5, 25);

  // Simply setting the required parameters.
  var target = popup.createAttachTarget(anchor);
  assertTrue(popup.isAttachTarget(anchor));
  assertTarget(target, anchor, undefined, undefined,
      goog.events.EventType.MOUSEDOWN, undefined);

  // Creating another target with all the parameters.
  target = popup.createAttachTarget(anchor, targetCorner, menuCorner,
      contextMenu, margin);
  assertTrue(popup.isAttachTarget(anchor));
  assertTarget(target, anchor, targetCorner, menuCorner,
      goog.events.EventType.MOUSEDOWN, margin);

  // Finally, switch up the 'contextMenu'
  target = popup.createAttachTarget(anchor, undefined, undefined,
      true /*opt_contextMenu*/, undefined);
  assertTarget(target, anchor, undefined, undefined,
      goog.events.EventType.CONTEXTMENU, undefined);
}


/**
 * Tests the behavior of {@link PopupMenu.getAttachTarget}.
 */
function testGetAttachTarget() {
  // Before the menu is attached to the anchor.
  var target = popup.getAttachTarget(anchor);
  assertTrue('Not expecting a target before the element is attach to the menu',
      target == null);

  // Randomly picking parameters.
  var targetCorner = goog.positioning.Corner.TOP_END;
  var menuCorner = goog.positioning.Corner.BOTTOM_LEFT;
  var contextMenu = false;   // Show menu on mouse down event.
  var margin = new goog.math.Box(0, 10, 5, 25);

  popup.attach(anchor, targetCorner, menuCorner, contextMenu, margin);
  target = popup.getAttachTarget(anchor);
  assertTrue('Failed to get target after attaching element to menu',
      target != null);

  // Make sure we got the right target back.
  assertTarget(target, anchor, targetCorner, menuCorner,
      goog.events.EventType.MOUSEDOWN, margin);
}

function testSmallViewportSliding() {
  popup.getElement().style.position = 'absolute';
  popup.getElement().style.outline = '1px solid blue';
  var item = new goog.ui.MenuItem('Test Item');
  popup.addChild(item, true);
  item.getElement().style.overflow = 'hidden';

  var viewport = goog.style.getClientViewportElement();
  var viewportRect = goog.style.getVisibleRectForElement(viewport);

  var middlePos = Math.floor((viewportRect.right - viewportRect.left) / 2);
  var leftwardPos = Math.floor((viewportRect.right - viewportRect.left) / 3);
  var rightwardPos =
      Math.floor((viewportRect.right - viewportRect.left) / 3 * 2);

  // Can interpret these positions as widths relative to the viewport as well.
  var smallWidth = leftwardPos;
  var mediumWidth = middlePos;
  var largeWidth = rightwardPos;

  // Test small menu first.  This should be small enough that it will display
  // its upper left corner where we tell it to in all three positions.
  popup.getElement().style.width = smallWidth + 'px';

  var target = popup.createAttachTarget(anchor);
  popup.attach(anchor);

  popup.showMenu(target, leftwardPos, 0);
  assertObjectEquals(
      'Popup in wrong position: small size, leftward pos',
      new goog.math.Coordinate(leftwardPos, 0),
      goog.style.getPosition(popup.getElement()));

  popup.showMenu(target, middlePos, 0);
  assertObjectEquals(
      'Popup in wrong position: small size, middle pos',
      new goog.math.Coordinate(middlePos, 0),
      goog.style.getPosition(popup.getElement()));

  popup.showMenu(target, rightwardPos, 0);
  assertObjectEquals(
      'Popup in wrong position: small size, rightward pos',
      new goog.math.Coordinate(rightwardPos, 0),
      goog.style.getPosition(popup.getElement()));

  // Test medium menu next.  This should display with its upper left corner
  // at the target when leftward and middle, but on the right it should
  // position its upper right corner at the target instead.
  popup.getElement().style.width = mediumWidth + 'px';

  popup.showMenu(target, leftwardPos, 0);
  assertObjectEquals(
      'Popup in wrong position: medium size, leftward pos',
      new goog.math.Coordinate(leftwardPos, 0),
      goog.style.getPosition(popup.getElement()));

  popup.showMenu(target, middlePos, 0);
  assertObjectEquals(
      'Popup in wrong position: medium size, middle pos',
      new goog.math.Coordinate(middlePos, 0),
      goog.style.getPosition(popup.getElement()));

  popup.showMenu(target, rightwardPos, 0);
  assertObjectEquals(
      'Popup in wrong position: medium size, rightward pos',
      new goog.math.Coordinate(rightwardPos - mediumWidth, 0),
      goog.style.getPosition(popup.getElement()));

  // Test large menu next.  This should display with its upper left corner at
  // the target when leftward, and its upper right corner at the target when
  // rightward, but right in the middle neither corner can be at the target and
  // keep the entire menu onscreen, so it should place its upper right corner
  // at the very right edge of the viewport.
  popup.getElement().style.width = largeWidth + 'px';
  popup.showMenu(target, leftwardPos, 0);
  assertObjectEquals(
      'Popup in wrong position: large size, leftward pos',
      new goog.math.Coordinate(leftwardPos, 0),
      goog.style.getPosition(popup.getElement()));

  popup.showMenu(target, middlePos, 0);
  assertObjectEquals(
      'Popup in wrong position: large size, middle pos',
      new goog.math.Coordinate(
          viewportRect.right - viewportRect.left - largeWidth, 0),
      goog.style.getPosition(popup.getElement()));

  popup.showMenu(target, rightwardPos, 0);
  assertObjectEquals(
      'Popup in wrong position: large size, rightward pos',
      new goog.math.Coordinate(rightwardPos - largeWidth, 0),
      goog.style.getPosition(popup.getElement()));

  // Make sure that the menu still displays correctly if we give the target
  // a target corner.  We can't set the overflow policy in that case, but it
  // should still display.
  popup.detach(anchor);
  anchor.style.position = 'absolute';
  anchor.style.left = '24px';
  anchor.style.top = '24px';
  var targetCorner = goog.positioning.Corner.TOP_END;
  target = popup.createAttachTarget(anchor, targetCorner);
  popup.attach(anchor, targetCorner);
  popup.getElement().style.width = smallWidth + 'px';
  popup.showMenu(target, leftwardPos, 0);
  assertObjectEquals(
      'Popup in wrong position: small size, leftward pos, with target corner',
      new goog.math.Coordinate(24, 24),
      goog.style.getPosition(popup.getElement()));
}
