/**
 * @license
 * Copyright The Closure Library Authors.
 * SPDX-License-Identifier: Apache-2.0
 */

goog.module('goog.events.BrowserEventTest');
goog.setTestOnly();

const BrowserEvent = goog.require('goog.events.BrowserEvent');
const Coordinate = goog.require('goog.math.Coordinate');
const PropertyReplacer = goog.require('goog.testing.PropertyReplacer');
const recordFunction = goog.require('goog.testing.recordFunction');
const testSuite = goog.require('goog.testing.testSuite');
const userAgent = goog.require('goog.userAgent');

const stubs = new PropertyReplacer();
const Button = BrowserEvent.MouseButton;
const PointerType = BrowserEvent.PointerType;

/**
 * @param {string} type
 * @param {number} button
 * @param {boolean=} opt_ctrlKey
 * @return {!BrowserEvent}
 * @suppress {checkTypes} suppression added to enable type checking
 */
function createMouseEvent(type, button, opt_ctrlKey) {
  return new BrowserEvent({type: type, button: button, ctrlKey: !!opt_ctrlKey});
}

/**
 * @param {string} type
 * @param {!Element} target
 * @param {!Coordinate} clientCoords
 * @param {!Coordinate} screenCoords
 * @return {!BrowserEvent}
 * @suppress {checkTypes} suppression added to enable type checking
 */
function createTouchEvent(type, target, clientCoords, screenCoords) {
  return new BrowserEvent({
    type: type,
    target: target,
    changedTouches: [{
      clientX: clientCoords.x,
      clientY: clientCoords.y,
      screenX: screenCoords.x,
      screenY: screenCoords.y,
    }],
  });
}

/**
 * @param {string} type
 * @param {number} pointerId
 * @param {string} pointerType
 * @return {!BrowserEvent}
 * @suppress {checkTypes} suppression added to enable type checking
 */
function createPointerEvent(type, pointerId, pointerType) {
  return new BrowserEvent(
      {type: type, pointerId: pointerId, pointerType: pointerType});
}

/**
 * @param {!BrowserEvent} event
 * @param {BrowserEvent.MouseButton} button
 * @param {boolean} isActionButton
 * @return {!BrowserEvent}
 * @suppress {missingReturn} suppression added to enable type checking
 */
function assertIsButton(event, button, isActionButton) {
  for (let key in Button) {
    assertEquals(
        'Testing isButton(' + key + ') against ' + button + ' and type ' +
            event.type,
        Button[key] == button, event.isButton(Button[key]));
  }

  assertEquals(isActionButton, event.isMouseActionButton());
}
testSuite({
  setUp() {
    stubs.reset();
  },

  /**
   * @see https://bugzilla.mozilla.org/show_bug.cgi?id=497780
   * @suppress {visibility} suppression added to enable type checking
   */
  testInvalidNodeBug() {
    if (!userAgent.GECKO) return;

    const event = {};
    event.relatedTarget = {};
    event.relatedTarget.__defineGetter__('nodeName', () => {
      throw new Error('https://bugzilla.mozilla.org/show_bug.cgi?id=497780');
    });
    assertThrows(/**
                    @suppress {missingProperties} suppression added to enable
                    type checking
                  */
                 () => event.relatedTarget.nodeName);

    /** @suppress {checkTypes} suppression added to enable type checking */
    const bEvent = new BrowserEvent(event);
    assertEquals(event, bEvent.event_);
    assertNull(bEvent.relatedTarget);
  },

  testPreventDefault() {
    const event = {};
    event.defaultPrevented = false;
    /** @suppress {checkTypes} suppression added to enable type checking */
    const bEvent = new BrowserEvent(event);
    assertFalse(bEvent.defaultPrevented);
    bEvent.preventDefault();
    assertTrue(bEvent.defaultPrevented);
  },

  testDefaultPrevented() {
    const event = {};
    event.defaultPrevented = true;
    event.preventDefault = recordFunction();
    /** @suppress {checkTypes} suppression added to enable type checking */
    const bEvent = new BrowserEvent(event);
    assertTrue(bEvent.defaultPrevented);
    assertEquals(0, event.preventDefault.getCallCount());
  },

  testIsButtonWebkitMac() {
    stubs.set(userAgent, 'WEBKIT', true);
    stubs.set(userAgent, 'MAC', true);
    assertIsButton(createMouseEvent('mousedown', 0), Button.LEFT, true);
    assertIsButton(createMouseEvent('mousedown', 0, true), Button.LEFT, false);
    assertIsButton(createMouseEvent('mousedown', 2), Button.RIGHT, false);
    assertIsButton(createMouseEvent('mousedown', 2, true), Button.RIGHT, false);
    assertIsButton(createMouseEvent('mousedown', 1), Button.MIDDLE, false);
    assertIsButton(
        createMouseEvent('mousedown', 1, true), Button.MIDDLE, false);
  },

  testIsButtonGecko() {
    stubs.set(userAgent, 'GECKO', true);
    stubs.set(userAgent, 'MAC', true);
    assertIsButton(createMouseEvent('mousedown', 0), Button.LEFT, true);
    assertIsButton(createMouseEvent('mousedown', 2, true), Button.RIGHT, false);
  },

  testTouchEventHandling() {
    const clientCoords = new Coordinate(5, 5);
    const screenCoords = new Coordinate(10, 10);
    const target = document.body;
    const touchStart =
        createTouchEvent('touchstart', target, clientCoords, screenCoords);
    const touchMove =
        createTouchEvent('touchmove', target, clientCoords, screenCoords);
    const touchEnd =
        createTouchEvent('touchend', target, clientCoords, screenCoords);
    const touchCancel =
        createTouchEvent('touchcancel', target, clientCoords, screenCoords);

    assertEquals(clientCoords.x, touchStart.clientX);
    assertEquals(clientCoords.y, touchStart.clientY);
    assertEquals(target, touchStart.target);

    assertEquals(screenCoords.x, touchMove.screenX);
    assertEquals(screenCoords.y, touchMove.screenY);

    assertEquals(clientCoords.x, touchEnd.clientX);
    assertEquals(clientCoords.y, touchEnd.clientY);

    assertEquals(screenCoords.x, touchCancel.screenX);
    assertEquals(screenCoords.y, touchCancel.screenY);
    assertEquals(target, touchCancel.target);
  },

  testGuardAgainstUndefinedTouchCoordinates() {
    /** @suppress {checkTypes} suppression added to enable type checking */
    const noChangedTouches = new BrowserEvent({
      type: 'touchstart',
      target: document.body,
      changedTouches: [],
    });

    /** @suppress {checkTypes} suppression added to enable type checking */
    const emptyTouchObject = new BrowserEvent({
      type: 'touchstart',
      target: document.body,
      changedTouches: [{}],
    });

    /** @suppress {checkTypes} suppression added to enable type checking */
    const onlyPageCoords = new BrowserEvent({
      type: 'touchstart',
      target: document.body,
      changedTouches: [{pageX: 6, pageY: 7}],
    });

    assertEquals(undefined, noChangedTouches.clientX);
    assertEquals(undefined, noChangedTouches.clientY);
    assertEquals(0, noChangedTouches.screenX);
    assertEquals(0, noChangedTouches.screenY);

    assertEquals(undefined, emptyTouchObject.clientX);
    assertEquals(undefined, emptyTouchObject.clientY);
    assertEquals(0, emptyTouchObject.screenX);
    assertEquals(0, emptyTouchObject.screenY);

    assertEquals(6, onlyPageCoords.clientX);
    assertEquals(7, onlyPageCoords.clientY);
    assertEquals(0, onlyPageCoords.screenX);
    assertEquals(0, onlyPageCoords.screenY);
  },

  testPointerEvent() {
    const event = createPointerEvent('pointerdown', 123, PointerType.MOUSE);
    assertEquals(123, event.pointerId);
    assertEquals(PointerType.MOUSE, event.pointerType);
  },

  testMSPointerEvent() {
    /** @suppress {checkTypes} suppression added to enable type checking */
    const event = createPointerEvent('MSPointerDown', 123, 4 /* mouse */);
    assertEquals(123, event.pointerId);
    assertEquals(PointerType.MOUSE, event.pointerType);
  },

  testUnsupportedPointerEvent() {
    const event = createMouseEvent('mousedown', 1);
    assertEquals(0, event.pointerId);
    assertEquals('', event.pointerType);
  },
});
