// 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.AdvancedTooltipTest');
goog.setTestOnly('goog.ui.AdvancedTooltipTest');

goog.require('goog.dom');
goog.require('goog.events.Event');
goog.require('goog.events.EventType');
goog.require('goog.math.Box');
goog.require('goog.math.Coordinate');
goog.require('goog.style');
goog.require('goog.testing.MockClock');
goog.require('goog.testing.events');
goog.require('goog.testing.jsunit');
goog.require('goog.ui.AdvancedTooltip');
goog.require('goog.ui.Tooltip');
goog.require('goog.userAgent');

var att;
var clock;
var anchor;
var elsewhere;
var popup;

var SHOWDELAY = 50;
var HIDEDELAY = 250;
var TRACKINGDELAY = 100;

function isWindowTooSmall() {
  // Firefox 3 fails if the window is too small.
  return goog.userAgent.GECKO &&
      (window.innerWidth < 350 || window.innerHeight < 100);
}

function setUp() {
  popup = goog.dom.createDom('span',
      {id: 'popup', style: 'position:absolute;top:300;left:300'}, 'Hello');
  att = new goog.ui.AdvancedTooltip('hovertarget');
  att.setElement(popup);
  att.setCursorTracking(true);
  att.setHotSpotPadding(new goog.math.Box(10, 10, 10, 10));
  att.setShowDelayMs(SHOWDELAY);
  att.setHideDelayMs(HIDEDELAY);
  att.setCursorTrackingHideDelayMs(TRACKINGDELAY);
  att.setMargin(new goog.math.Box(300, 0, 0, 300));

  clock = new goog.testing.MockClock(true);

  anchor = goog.dom.getElement('hovertarget');
  elsewhere = goog.dom.getElement('notpopup');
}

function tearDown() {
  // tooltip needs to be hidden as well as disposed of so that it doesn't
  // leave global state hanging around to trip up other tests.
  if (att.isVisible()) {
    att.onHide_();
  }
  att.dispose();
  clock.uninstall();
}

function assertVisible(msg, element) {
  if (element) {
    assertEquals(msg, 'visible', element.style.visibility);
  } else {
    assertEquals('visible', msg.style.visibility);
  }
}

function assertHidden(msg, element) {
  if (element) {
    assertEquals(msg, 'hidden', element.style.visibility);
  } else {
    assertEquals('hidden', msg.style.visibility);
  }
}


/**
 * Helper function to fire events related to moving a mouse from one element
 * to another. Fires mouseout, mouseover, and mousemove event.
 * @param {Element} from Element the mouse is moving from.
 * @param {Element} to Element the mouse is moving to.
 */
function fireMouseEvents(from, to) {
  goog.testing.events.fireMouseOutEvent(from, to);
  goog.testing.events.fireMouseOverEvent(to, from);
  var bounds = goog.style.getBounds(to);
  goog.testing.events.fireMouseMoveEvent(
      document, new goog.math.Coordinate(bounds.left + 1, bounds.top + 1));
}

function testCursorTracking() {
  if (isWindowTooSmall()) {
    return;
  }

  var oneThirdOfTheWay, twoThirdsOfTheWay;

  oneThirdOfTheWay = new goog.math.Coordinate(100, 100);
  twoThirdsOfTheWay = new goog.math.Coordinate(200, 200);

  goog.testing.events.fireMouseOverEvent(anchor, elsewhere);
  clock.tick(SHOWDELAY);
  assertVisible('Mouse over anchor should show popup', popup);

  goog.testing.events.fireMouseOutEvent(anchor, elsewhere);
  goog.testing.events.fireMouseMoveEvent(document, oneThirdOfTheWay);
  clock.tick(HIDEDELAY);
  assertVisible("Moving mouse towards popup shouldn't hide it", popup);

  goog.testing.events.fireMouseMoveEvent(document, twoThirdsOfTheWay);
  goog.testing.events.fireMouseMoveEvent(document, oneThirdOfTheWay);
  clock.tick(TRACKINGDELAY);
  assertHidden('Moving mouse away from popup should hide it', popup);

  goog.testing.events.fireMouseMoveEvent(document, twoThirdsOfTheWay);
  goog.testing.events.fireBrowserEvent(new goog.events.Event(
      goog.events.EventType.FOCUS, anchor));
  clock.tick(SHOWDELAY);
  assertVisible('Set focus shows popup', popup);
  goog.testing.events.fireMouseMoveEvent(document, oneThirdOfTheWay);
  clock.tick(TRACKINGDELAY);
  assertHidden('Mouse move after focus should hide popup', popup);
}

function testPadding() {
  if (isWindowTooSmall()) {
    return;
  }

  goog.testing.events.fireMouseOverEvent(anchor, elsewhere);
  clock.tick(SHOWDELAY);

  var attBounds = goog.style.getBounds(popup);
  var inPadding = new goog.math.Coordinate(attBounds.left - 5,
                                           attBounds.top - 5);
  var outOfPadding = new goog.math.Coordinate(attBounds.left - 15,
                                              attBounds.top - 15);

  fireMouseEvents(anchor, popup);
  goog.testing.events.fireMouseOutEvent(popup, elsewhere);
  goog.testing.events.fireMouseMoveEvent(document, inPadding);
  clock.tick(HIDEDELAY);
  assertVisible("Mouse out of popup but within padding shouldn't hide it",
                popup);

  goog.testing.events.fireMouseMoveEvent(document, outOfPadding);
  clock.tick(HIDEDELAY);
  assertHidden('Mouse move beyond popup padding should hide it', popup);
}


function testAnchorWithChild() {
  var child = goog.dom.getElement('childtarget');

  fireMouseEvents(elsewhere, anchor);
  fireMouseEvents(anchor, child);
  clock.tick(SHOWDELAY);
  assertVisible('Mouse into child of anchor should still show popup', popup);

  fireMouseEvents(child, anchor);
  clock.tick(HIDEDELAY);
  assertVisible('Mouse from child to anchor should still show popup', popup);
}

function testNestedTooltip() {
  if (!isWindowTooSmall()) {
    checkNestedTooltips(false);
  }
}

function testNestedAdvancedTooltip() {
  if (!isWindowTooSmall()) {
    checkNestedTooltips(true);
  }
}

function testResizingTooltipWhileShown() {
  fireMouseEvents(elsewhere, anchor);
  clock.tick(SHOWDELAY);
  popup.style.height = '100px';
  var attBounds = goog.style.getBounds(popup);
  var inPadding = new goog.math.Coordinate(
      attBounds.left + 5, attBounds.top + attBounds.height + 5);
  var outOfPadding = new goog.math.Coordinate(
      attBounds.left + 5, attBounds.top + attBounds.height + 15);

  fireMouseEvents(anchor, popup);
  goog.testing.events.fireMouseOutEvent(popup, elsewhere);
  goog.testing.events.fireMouseMoveEvent(document, inPadding);
  clock.tick(HIDEDELAY);
  assertVisible("Mouse out of popup but within padding shouldn't hide it",
                popup);

  goog.testing.events.fireMouseMoveEvent(document, outOfPadding);
  clock.tick(HIDEDELAY);
  assertHidden('Mouse move beyond popup padding should hide it', popup);
}

function checkNestedTooltips(useAdvancedTooltip) {
  popup.appendChild(goog.dom.createDom(
      'span', {id: 'nestedAnchor'}, 'Nested Anchor'));
  var nestedAnchor = goog.dom.getElement('nestedAnchor');
  var nestedTooltip;
  if (useAdvancedTooltip) {
    nestedTooltip = new goog.ui.AdvancedTooltip(nestedAnchor, 'popup');
  } else {
    nestedTooltip = new goog.ui.Tooltip(nestedAnchor, 'popup');
  }
  var nestedPopup = nestedTooltip.getElement();
  nestedTooltip.setShowDelayMs(SHOWDELAY);
  nestedTooltip.setHideDelayMs(HIDEDELAY);

  fireMouseEvents(elsewhere, anchor);
  clock.tick(SHOWDELAY);
  fireMouseEvents(anchor, popup);
  fireMouseEvents(popup, nestedAnchor);
  clock.tick(SHOWDELAY + HIDEDELAY);
  assertVisible('Mouse into nested anchor should show popup', nestedPopup);
  assertVisible('Mouse into nested anchor should not hide parent', popup);
  fireMouseEvents(nestedAnchor, elsewhere);
  clock.tick(HIDEDELAY);
  assertHidden('Mouse out of nested popup should hide it', nestedPopup);
  clock.tick(HIDEDELAY);
  assertHidden('Mouse out of nested popup should eventually hide parent',
               popup);

  goog.testing.events.fireBrowserEvent(new goog.events.Event(
      goog.events.EventType.FOCUS, anchor));
  clock.tick(SHOWDELAY);
  goog.testing.events.fireBrowserEvent(new goog.events.Event(
      goog.events.EventType.BLUR, anchor));
  goog.testing.events.fireBrowserEvent(new goog.events.Event(
      goog.events.EventType.FOCUS, nestedAnchor));
  clock.tick(SHOWDELAY + HIDEDELAY);
  assertVisible("Moving focus to child anchor doesn't hide parent", popup);
  assertVisible('Set focus shows nested popup', nestedPopup);

  goog.testing.events.fireBrowserEvent(new goog.events.Event(
      goog.events.EventType.BLUR, nestedAnchor));
  goog.testing.events.fireBrowserEvent(new goog.events.Event(
      goog.events.EventType.FOCUS, anchor));
  clock.tick(HIDEDELAY + HIDEDELAY);
  assertHidden('Lose focus hides nested popup', nestedPopup);
  assertVisible(
      "Moving focus from nested anchor to parent doesn't hide parent", popup);

  goog.testing.events.fireBrowserEvent(new goog.events.Event(
      goog.events.EventType.BLUR, anchor));
  goog.testing.events.fireBrowserEvent(new goog.events.Event(
      goog.events.EventType.FOCUS, nestedAnchor));
  clock.tick(SHOWDELAY);
  goog.testing.events.fireBrowserEvent(new goog.events.Event(
      goog.events.EventType.BLUR, nestedAnchor));
  clock.tick(HIDEDELAY);
  assertHidden('Lose focus hides nested popup', nestedPopup);
  clock.tick(HIDEDELAY);
  assertHidden('Nested anchor losing focus hides parent', popup);

  goog.testing.events.fireBrowserEvent(new goog.events.Event(
      goog.events.EventType.FOCUS, anchor));
  clock.tick(SHOWDELAY);
  goog.testing.events.fireBrowserEvent(new goog.events.Event(
      goog.events.EventType.BLUR, anchor));
  goog.testing.events.fireBrowserEvent(new goog.events.Event(
      goog.events.EventType.FOCUS, nestedAnchor));
  clock.tick(SHOWDELAY);
  var coordElsewhere = new goog.math.Coordinate(1, 1);
  goog.testing.events.fireMouseMoveEvent(document, coordElsewhere);
  clock.tick(HIDEDELAY);
  assertHidden('Mouse move should hide parent with active child', popup);
  assertHidden('Mouse move should hide nested popup', nestedPopup);
}
