// Copyright 2009 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.events.MouseWheelHandlerTest');
goog.setTestOnly('goog.events.MouseWheelHandlerTest');

goog.require('goog.dom');
goog.require('goog.events');
goog.require('goog.events.BrowserEvent');
goog.require('goog.events.MouseWheelEvent');
goog.require('goog.events.MouseWheelHandler');
goog.require('goog.functions');
goog.require('goog.string');
goog.require('goog.testing.PropertyReplacer');
goog.require('goog.testing.events');
goog.require('goog.testing.jsunit');
goog.require('goog.userAgent');

var log;
var stubs = new goog.testing.PropertyReplacer();

var DEFAULT_TYPE = 'mousewheel';
var GECKO_TYPE = 'DOMMouseScroll';

var HORIZONTAL = 'h';
var VERTICAL = 'v';

var mouseWheelEvent;
var mouseWheelEventRtl;
var mouseWheelHandler;
var mouseWheelHandlerRtl;

function setUpPage() {
  log = goog.dom.getElement('log');
}

function setUp() {
  stubs.remove(goog, 'userAgent');
}

function tearDown() {
  stubs.reset();
  goog.dispose(mouseWheelHandler);
  goog.dispose(mouseWheelHandlerRtl);
  mouseWheelHandlerRtl = null;
  mouseWheelHandler = null;
  mouseWheelEvent = null;
  mouseWheelEventRtl = null;
}

function tearDownPage() {
  // Create interactive demo.
  mouseWheelHandler = new goog.events.MouseWheelHandler(document.body);

  goog.events.listen(
      mouseWheelHandler, goog.events.MouseWheelHandler.EventType.MOUSEWHEEL,
      function(e) {
        log.innerHTML += goog.string.subs(
            '<br />(deltaX, deltaY): (%s, %s)', e.deltaX, e.deltaY);
      });
}

function testIeStyleMouseWheel() {
  goog.userAgent =
      {OPERA: false, EDGE_OR_IE: true, GECKO: false, WEBKIT: false};

  createHandlerAndListen();

  // Non-gecko, non-webkit events get wheelDelta divided by -40 to get detail.
  handleEvent(createFakeMouseWheelEvent(DEFAULT_TYPE, 120));
  assertMouseWheelEvent(-3, 0, -3);

  handleEvent(createFakeMouseWheelEvent(DEFAULT_TYPE, -120));
  assertMouseWheelEvent(3, 0, 3);

  handleEvent(createFakeMouseWheelEvent(DEFAULT_TYPE, 1200));
  assertMouseWheelEvent(-30, 0, -30);
}

function testNullBody() {
  goog.userAgent = {OPERA: false, IE: true, GECKO: false, WEBKIT: false};
  var documentObjectWithNoBody = {};
  goog.testing.events.mixinListenable(documentObjectWithNoBody);
  mouseWheelHandler =
      new goog.events.MouseWheelHandler(documentObjectWithNoBody);
}

function testGeckoStyleMouseWheel() {
  goog.userAgent = {OPERA: false, IE: false, GECKO: true, WEBKIT: false};

  createHandlerAndListen();

  handleEvent(createFakeMouseWheelEvent(GECKO_TYPE, null, 3));
  assertMouseWheelEvent(3, 0, 3);

  handleEvent(createFakeMouseWheelEvent(GECKO_TYPE, null, -12));
  assertMouseWheelEvent(-12, 0, -12);

  // Really big values should get truncated to +-3.
  handleEvent(createFakeMouseWheelEvent(GECKO_TYPE, null, 1200));
  assertMouseWheelEvent(3, 0, 3);

  handleEvent(createFakeMouseWheelEvent(GECKO_TYPE, null, -1200));
  assertMouseWheelEvent(-3, 0, -3);

  // Test scrolling with the additional axis property.
  handleEvent(createFakeMouseWheelEvent(GECKO_TYPE, null, 3, VERTICAL));
  assertMouseWheelEvent(3, 0, 3);

  handleEvent(createFakeMouseWheelEvent(GECKO_TYPE, null, 3, HORIZONTAL));
  assertMouseWheelEvent(3, 3, 0);

  handleEvent(createFakeMouseWheelEvent(GECKO_TYPE, null, -3, HORIZONTAL));
  assertMouseWheelEvent(-3, -3, 0);
}

function testWebkitStyleMouseWheel_ieStyle() {
  goog.userAgent =
      {OPERA: false, IE: false, GECKO: false, WEBKIT: true, WINDOWS: true};

  createHandlerAndListen();

  // IE-style Webkit events get wheelDelta divided by -40 to get detail.
  handleEvent(createFakeWebkitMouseWheelEvent(-40, 0));
  assertMouseWheelEvent(1, 1, 0);

  handleEvent(createFakeWebkitMouseWheelEvent(120, 0));
  assertMouseWheelEvent(-3, -3, 0);

  handleEvent(createFakeWebkitMouseWheelEvent(0, 120));
  assertMouseWheelEvent(-3, 0, -3);

  handleEvent(createFakeWebkitMouseWheelEvent(0, -40));
  assertMouseWheelEvent(1, 0, 1);

  handleEvent(createFakeWebkitMouseWheelEvent(80, -40));
  assertMouseWheelEvent(-2, -2, 1);
}

function testWebkitStyleMouseWheel_ieStyleOnLinux() {
  goog.userAgent = {
    OPERA: false,
    IE: false,
    GECKO: false,
    WEBKIT: true,
    WINDOWS: false,
    LINUX: true
  };
  runWebKitContinuousAndDiscreteEventsTest();
}

function testWebkitStyleMouseWheel_ieStyleOnMac() {
  goog.userAgent = {
    OPERA: false,
    IE: false,
    GECKO: false,
    WEBKIT: true,
    WINDOWS: false,
    MAC: true
  };
  runWebKitContinuousAndDiscreteEventsTest();
}

function runWebKitContinuousAndDiscreteEventsTest() {
  goog.userAgent.isVersionOrHigher = goog.functions.TRUE;

  createHandlerAndListen();

  // IE-style wheel events.
  handleEvent(createFakeWebkitMouseWheelEvent(0, -40));
  assertMouseWheelEvent(1, 0, 1);

  handleEvent(createFakeWebkitMouseWheelEvent(80, -40));
  assertMouseWheelEvent(-2, -2, 1);

  // Even in Webkit versions that usually behave in IE style, sometimes wheel
  // events don't behave; this has been observed for instance with Macbook
  // and Chrome OS touchpads in Webkit 534.10+.
  handleEvent(createFakeWebkitMouseWheelEvent(-3, 5));
  assertMouseWheelEvent(-5, 3, -5);

  handleEvent(createFakeWebkitMouseWheelEvent(4, -7));
  assertMouseWheelEvent(7, -4, 7);
}

function testWebkitStyleMouseWheel_nonIeStyle() {
  goog.userAgent =
      {OPERA: false, IE: false, GECKO: false, WEBKIT: true, WINDOWS: false};

  goog.userAgent.isVersionOrHigher = goog.functions.FALSE;

  createHandlerAndListen();

  // non-IE-style Webkit events do not get wheelDelta scaled
  handleEvent(createFakeWebkitMouseWheelEvent(-40, 0));
  assertMouseWheelEvent(1, 1, 0);

  handleEvent(createFakeWebkitMouseWheelEvent(120, 0));
  assertMouseWheelEvent(-3, -3, 0);

  handleEvent(createFakeWebkitMouseWheelEvent(0, 120));
  assertMouseWheelEvent(-3, 0, -3);

  handleEvent(createFakeWebkitMouseWheelEvent(0, -40));
  assertMouseWheelEvent(1, 0, 1);

  handleEvent(createFakeWebkitMouseWheelEvent(80, -40));
  assertMouseWheelEvent(-2, -2, 1);
}

function testMaxDeltaX() {
  goog.userAgent =
      {OPERA: false, IE: false, GECKO: false, WEBKIT: true, WINDOWS: true};

  createHandlerAndListen();

  // IE-style Webkit events get wheelDelta divided by -40 to get detail.
  handleEvent(createFakeWebkitMouseWheelEvent(-120, 0));
  assertMouseWheelEvent(3, 3, 0);

  mouseWheelHandler.setMaxDeltaX(3);
  mouseWheelHandlerRtl.setMaxDeltaX(3);
  handleEvent(createFakeWebkitMouseWheelEvent(-120, 0));
  assertMouseWheelEvent(3, 3, 0);

  mouseWheelHandler.setMaxDeltaX(2);
  mouseWheelHandlerRtl.setMaxDeltaX(2);
  handleEvent(createFakeWebkitMouseWheelEvent(-120, 0));
  assertMouseWheelEvent(3, 2, 0);

  handleEvent(createFakeWebkitMouseWheelEvent(0, -120));
  assertMouseWheelEvent(3, 0, 3);
}

function testMaxDeltaY() {
  goog.userAgent =
      {OPERA: false, IE: false, GECKO: false, WEBKIT: true, WINDOWS: true};

  createHandlerAndListen();

  // IE-style Webkit events get wheelDelta divided by -40 to get detail.
  handleEvent(createFakeWebkitMouseWheelEvent(0, -120));
  assertMouseWheelEvent(3, 0, 3);

  mouseWheelHandler.setMaxDeltaY(3);
  mouseWheelHandlerRtl.setMaxDeltaY(3);
  handleEvent(createFakeWebkitMouseWheelEvent(0, -120));
  assertMouseWheelEvent(3, 0, 3);

  mouseWheelHandler.setMaxDeltaY(2);
  mouseWheelHandlerRtl.setMaxDeltaY(2);
  handleEvent(createFakeWebkitMouseWheelEvent(0, -120));
  assertMouseWheelEvent(3, 0, 2);

  handleEvent(createFakeWebkitMouseWheelEvent(-120, 0));
  assertMouseWheelEvent(3, 3, 0);
}

// Be sure to call this after setting up goog.userAgent mock and not before.
function createHandlerAndListen() {
  mouseWheelHandler =
      new goog.events.MouseWheelHandler(goog.dom.getElement('foo'));

  goog.events.listen(
      mouseWheelHandler, goog.events.MouseWheelHandler.EventType.MOUSEWHEEL,
      function(e) { mouseWheelEvent = e; });

  mouseWheelHandlerRtl =
      new goog.events.MouseWheelHandler(goog.dom.getElement('fooRtl'));

  goog.events.listen(
      mouseWheelHandlerRtl, goog.events.MouseWheelHandler.EventType.MOUSEWHEEL,
      function(e) { mouseWheelEventRtl = e; });
}

function handleEvent(event) {
  mouseWheelHandler.handleEvent(event);
  mouseWheelHandlerRtl.handleEvent(event);
}

function assertMouseWheelEvent(expectedDetail, expectedDeltaX, expectedDeltaY) {
  assertTrue('event should be non-null', !!mouseWheelEvent);
  assertTrue(
      'event should have correct JS type',
      mouseWheelEvent instanceof goog.events.MouseWheelEvent);
  assertEquals(
      'event should have correct detail property', expectedDetail,
      mouseWheelEvent.detail);
  assertEquals(
      'event should have correct deltaX property', expectedDeltaX,
      mouseWheelEvent.deltaX);
  assertEquals(
      'event should have correct deltaY property', expectedDeltaY,
      mouseWheelEvent.deltaY);

  // RTL
  assertTrue('event should be non-null', !!mouseWheelEventRtl);
  assertTrue(
      'event should have correct JS type',
      mouseWheelEventRtl instanceof goog.events.MouseWheelEvent);
  assertEquals(
      'event should have correct detail property', expectedDetail,
      mouseWheelEventRtl.detail);
  assertEquals(
      'event should have correct deltaX property', -expectedDeltaX,
      mouseWheelEventRtl.deltaX);
  assertEquals(
      'event should have correct deltaY property', expectedDeltaY,
      mouseWheelEventRtl.deltaY);
}

function createFakeMouseWheelEvent(
    type, opt_wheelDelta, opt_detail, opt_axis, opt_wheelDeltaX,
    opt_wheelDeltaY) {
  var event = {
    type: type,
    wheelDelta: goog.isDef(opt_wheelDelta) ? opt_wheelDelta : undefined,
    detail: goog.isDef(opt_detail) ? opt_detail : undefined,
    axis: opt_axis || undefined,
    wheelDeltaX: goog.isDef(opt_wheelDeltaX) ? opt_wheelDeltaX : undefined,
    wheelDeltaY: goog.isDef(opt_wheelDeltaY) ? opt_wheelDeltaY : undefined,

    // These two are constants defined on the event in FF3.1 and later.
    // It doesn't matter exactly what they are, and it doesn't affect
    // our simulations of other browsers.
    HORIZONTAL_AXIS: HORIZONTAL,
    VERTICAL_AXIS: VERTICAL
  };
  return new goog.events.BrowserEvent(event);
}

function createFakeWebkitMouseWheelEvent(wheelDeltaX, wheelDeltaY) {
  return createFakeMouseWheelEvent(
      DEFAULT_TYPE,
      Math.abs(wheelDeltaX) > Math.abs(wheelDeltaY) ? wheelDeltaX : wheelDeltaY,
      undefined, undefined, wheelDeltaX, wheelDeltaY);
}
