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

goog.require('goog.dom');
goog.require('goog.events');
goog.require('goog.events.BrowserEvent');
goog.require('goog.events.EventTarget');
goog.require('goog.events.EventType');
goog.require('goog.events.KeyCodes');
goog.require('goog.events.PasteHandler');
goog.require('goog.testing.MockClock');
goog.require('goog.testing.MockUserAgent');
goog.require('goog.testing.jsunit');
goog.require('goog.userAgent');

function setUp() {
  // TODO(user): fix {@code goog.testing.MockUserAgent} to do the right thing.
  // the code doesn't seem to be updating the variables with
  // goog.userAgent.init_(), which means it is not allowing me to mock the
  // user agent variables.
  goog.userAgent.GECKO = true;
  goog.userAgent.IE = false;
  goog.userAgent.WEBKIT = false;
  goog.userAgent.VERSION = '1.8';

  textarea = new goog.events.EventTarget();
  textarea.value = '';
  clock = new goog.testing.MockClock(true);
  mockUserAgent = new goog.testing.MockUserAgent();
  handler = new goog.events.PasteHandler(textarea);
  pasted = false;
  goog.events.listen(handler, goog.events.PasteHandler.EventType.PASTE,
      function() {
        pasted = true;
      });
}

function tearDown() {
  textarea.dispose();
  handler.dispose();
  clock.dispose();
  mockUserAgent.dispose();
}

function newBrowserEvent(type) {
  if (goog.isString(type)) {
    return new goog.events.BrowserEvent({type: type});
  } else {
    return new goog.events.BrowserEvent(type);
  }
}

function testDispatchingPasteEventSupportedByAFewBrowsersWork() {
  goog.userAgent.IE = true;
  var handlerThatSupportsPasteEvents =
      new goog.events.PasteHandler(textarea);
  // user clicks on the textarea and give it focus
  goog.events.listen(handlerThatSupportsPasteEvents,
      goog.events.PasteHandler.EventType.PASTE,
      function() {
        pasted = true;
      });
  textarea.dispatchEvent(newBrowserEvent('paste'));
  assertTrue(pasted);
}

function testJustTypingDoesntFirePasteEvent() {
  // user clicks on the textarea and give it focus
  textarea.dispatchEvent(newBrowserEvent(goog.events.EventType.FOCUS));
  assertFalse(pasted);
  // user starts typing
  textarea.dispatchEvent(newBrowserEvent({
    type: goog.events.EventType.KEYDOWN,
    keyCode: goog.events.KeyCodes.A
  }));
  textarea.value = 'a';
  assertFalse(pasted);

  // still typing
  textarea.dispatchEvent({
    type: goog.events.EventType.KEYDOWN,
    keyCode: goog.events.KeyCodes.B
  });
  textarea.value = 'ab';
  assertFalse(pasted);

  // ends typing
  textarea.dispatchEvent({
    type: goog.events.EventType.KEYDOWN,
    keyCode: goog.events.KeyCodes.C
  });
  textarea.value = 'abc';
  assertFalse(pasted);
}

function testStartsOnInitialState() {
  assertTrue(handler.getState() == goog.events.PasteHandler.State.INIT);
  assertFalse(pasted);
}

function testBlurOnInit() {
  textarea.dispatchEvent(goog.events.EventType.BLUR);
  assertTrue(handler.getState() == goog.events.PasteHandler.State.INIT);
  assertFalse(pasted);
}

function testFocusOnInit() {
  textarea.dispatchEvent(goog.events.EventType.FOCUS);
  assertTrue(handler.getState() == goog.events.PasteHandler.State.FOCUSED);
  assertFalse(pasted);
}

function testInputOnFocus() {
  // user clicks on the textarea
  textarea.dispatchEvent(newBrowserEvent(goog.events.EventType.FOCUS));
  clock.tick(
      goog.events.PasteHandler.MANDATORY_MS_BETWEEN_INPUT_EVENTS_TIE_BREAKER +
      1);
  // and right click -> paste a text!
  textarea.dispatchEvent(newBrowserEvent('input'));
  assertTrue(handler.getState() == goog.events.PasteHandler.State.FOCUSED);
  // make sure we detected it
  assertTrue(pasted);
}

function testKeyPressOnFocus() {
  // user clicks on the textarea
  textarea.dispatchEvent(newBrowserEvent(goog.events.EventType.FOCUS));

  // starts typing something
  textarea.dispatchEvent(newBrowserEvent({
    type: goog.events.EventType.KEYDOWN,
    keyCode: goog.events.KeyCodes.A
  }));
  assertTrue(handler.getState() == goog.events.PasteHandler.State.TYPING);
  assertFalse(pasted);

  // and then presses ctrl+v
  textarea.dispatchEvent(newBrowserEvent({
    type: goog.events.EventType.KEYDOWN,
    keyCode: goog.events.KeyCodes.V,
    ctrlKey: true
  }));
  assertTrue(handler.getState() == goog.events.PasteHandler.State.TYPING);

  // makes sure we detected it
  assertTrue(pasted);
}

function testMouseOverOnInit() {
  // user has something on the events
  textarea.value = 'pasted string';
  // and right click -> paste it on the textarea, WITHOUT giving focus
  textarea.dispatchEvent(newBrowserEvent(goog.events.EventType.MOUSEOVER));
  assertTrue(handler.getState() == goog.events.PasteHandler.State.INIT);
  // makes sure we detect it
  assertTrue(pasted);

  pasted = false;

  // user normaly mouseovers the textarea, with no text change
  textarea.dispatchEvent(goog.events.EventType.MOUSEOVER);
  assertTrue(handler.getState() == goog.events.PasteHandler.State.INIT);
  // text area value doesnt change
  assertFalse(pasted);
}

function testMouseOverAfterTyping() {
  textarea.dispatchEvent(goog.events.EventType.FOCUS);
  assertFalse(pasted);
  textarea.dispatchEvent(
      {type: goog.events.EventType.KEYDOWN, keyCode: goog.events.KeyCodes.A});
  assertFalse(pasted);
  textarea.value = 'a';
  textarea.dispatchEvent('input');
  assertFalse(pasted);
  assertEquals('a', handler.oldValue_);
  textarea.dispatchEvent(goog.events.EventType.MOUSEOVER);
  assertFalse(pasted);
}

function testTypingAndThenRightClickPaste() {
  textarea.dispatchEvent(goog.events.EventType.FOCUS);

  textarea.dispatchEvent(
      {type: goog.events.EventType.KEYDOWN, keyCode: goog.events.KeyCodes.A});
  assertFalse(pasted);
  textarea.value = 'a';
  clock.tick(
      goog.events.PasteHandler.MANDATORY_MS_BETWEEN_INPUT_EVENTS_TIE_BREAKER +
      1);
  textarea.dispatchEvent('input');
  assertFalse(pasted);

  assertEquals('a', handler.oldValue_);

  textarea.value = 'ab';
  clock.tick(
      goog.events.PasteHandler.MANDATORY_MS_BETWEEN_INPUT_EVENTS_TIE_BREAKER +
      1);
  textarea.dispatchEvent(newBrowserEvent('input'));
  assertTrue(pasted);
}

function testTypingReallyFastDispatchesTwoInputEventsBeforeTheKeyDownEvent() {
  textarea.dispatchEvent(goog.events.EventType.FOCUS);

  // keydown and input events seems to be fired indepently: even though input
  // should happen after the key event, it doens't if the user types fast
  // enough. FF2 + linux doesn't fire keydown events for every key pressed when
  // you type fast enough. if one of the keydown events gets swallowed, two
  // input events are fired consecutively. notice that there is a similar
  // scenario, that actually does produce a valid paste action.
  // {@see testRightClickRightClickAlsoDispatchesTwoConsecutiveInputEvents}

  textarea.dispatchEvent(
      {type: goog.events.EventType.KEYDOWN, keyCode: goog.events.KeyCodes.A});
  assertFalse(pasted);
  textarea.value = 'a';
  clock.tick(
      goog.events.PasteHandler.MANDATORY_MS_BETWEEN_INPUT_EVENTS_TIE_BREAKER -
      1);
  textarea.dispatchEvent('input');
  assertFalse(pasted);

  // second key down events gets fired on a different order
  textarea.value = 'ab';
  clock.tick(
      goog.events.PasteHandler.MANDATORY_MS_BETWEEN_INPUT_EVENTS_TIE_BREAKER -
      1);
  textarea.dispatchEvent('input');
  assertFalse(pasted);
}

function testRightClickRightClickAlsoDispatchesTwoConsecutiveInputEvents() {
  textarea.dispatchEvent(goog.events.EventType.FOCUS);

  // there is also another case that two consecutive INPUT events are fired,
  // but in a valid paste action: if the user edit -> paste -> edit -> paste,
  // it is a valid paste action.

  textarea.value = 'a';
  clock.tick(
      goog.events.PasteHandler.MANDATORY_MS_BETWEEN_INPUT_EVENTS_TIE_BREAKER +
      1);
  textarea.dispatchEvent(newBrowserEvent('input'));
  assertTrue(pasted);

  // second key down events gets fired on a different order
  textarea.value = 'ab';
  clock.tick(
      goog.events.PasteHandler.MANDATORY_MS_BETWEEN_INPUT_EVENTS_TIE_BREAKER +
      1);
  textarea.dispatchEvent(newBrowserEvent('input'));
  assertTrue(pasted);
}

function testMiddleClickWithoutFocusTriggersPasteEvent() {
  // if the textarea is NOT selected, and then we use the middle button,
  // FF2+linux pastes what was last highlighted, causing a paste action.
  textarea.dispatchEvent(goog.events.EventType.FOCUS);
  textarea.dispatchEvent(newBrowserEvent('input'));
  assertTrue(pasted);
}


function testMacRightClickPasteRequiresCtrlBecauseItHasOneButton() {
  // Macs don't have two buttons mouse: this means that you need to press
  // ctrl + click to get to the menu, and then you can click paste.
  // The sequences of events fired on Opera are:
  // focus -> keydown (keyCode == 0, not e.ctrlKey) -> input
  goog.userAgent.OPERA = true;
  goog.userAgent.MAC = true;
  var handler = new goog.events.PasteHandler(textarea);
  // user clicks on the textarea and give it focus
  goog.events.listen(handler,
      goog.events.PasteHandler.EventType.PASTE,
      function() {
        pasted = true;
      });
  textarea.dispatchEvent(goog.events.EventType.FOCUS);
  assertFalse(pasted);
  textarea.dispatchEvent({type: goog.events.EventType.KEYDOWN, keyCode: 0});
  assertFalse(pasted);
  clock.tick(
      goog.events.PasteHandler.MANDATORY_MS_BETWEEN_INPUT_EVENTS_TIE_BREAKER +
      1);
  textarea.dispatchEvent(newBrowserEvent('input'));
  assertTrue(pasted);
}

function testOperaMacFiresKeyCode17WhenAppleKeyPressedButDoesNotFireKeyDown() {
  // Opera on Macs fires keycode 17 when apple key is pressed, and then it does
  // not fire a keydown event when the V is pressed.
  goog.userAgent.OPERA = true;
  goog.userAgent.MAC = true;
  var handler = new goog.events.PasteHandler(textarea);
  // user clicks on the textarea and give it focus
  goog.events.listen(handler,
      goog.events.PasteHandler.EventType.PASTE,
      function() {
        pasted = true;
      });
  textarea.dispatchEvent(goog.events.EventType.FOCUS);
  assertFalse(pasted);
  // apple key is pressed, generating a keydown event
  textarea.dispatchEvent({type: goog.events.EventType.KEYDOWN, keyCode: 17});
  assertFalse(pasted);
  clock.tick(
      goog.events.PasteHandler.MANDATORY_MS_BETWEEN_INPUT_EVENTS_TIE_BREAKER +
      1);
  // and then text is added magically without any extra keydown events.
  textarea.dispatchEvent(newBrowserEvent('input'));
  assertTrue(pasted);
}

function testScriptingDoesntTriggerPasteEvents() {
  var handlerUsedToListenForScriptingChanges =
      new goog.events.PasteHandler(textarea);
  pasted = false;
  // user clicks on the textarea and give it focus
  goog.events.listen(handlerUsedToListenForScriptingChanges,
      goog.events.PasteHandler.EventType.PASTE,
      function() {
        pasted = true;
      });
  goog.dom.getElement('foo').value = 'dear paste handler,';
  assertFalse(pasted);
  goog.dom.getElement('foo').value = 'please dont misunderstand script changes';
  assertFalse(pasted);
  goog.dom.getElement('foo').value = 'with user generated paste events';
  assertFalse(pasted);
  goog.dom.getElement('foo').value = 'thanks!';
  assertFalse(pasted);
}

function testAfterPaste() {
  goog.userAgent.IE = true;
  var handlerThatSupportsPasteEvents =
      new goog.events.PasteHandler(textarea);
  pasted = false;
  goog.events.listen(handlerThatSupportsPasteEvents,
      goog.events.PasteHandler.EventType.PASTE,
      function() {
        pasted = true;
      });
  var afterPasteFired = false;
  goog.events.listen(handlerThatSupportsPasteEvents,
      goog.events.PasteHandler.EventType.AFTER_PASTE,
      function() {
        afterPasteFired = true;
      });

  // Initial paste event comes before AFTER_PASTE has fired.
  textarea.dispatchEvent(newBrowserEvent('paste'));
  assertTrue(pasted);
  assertFalse(afterPasteFired);

  // Once text is pasted, it takes a bit to detect it, at which point
  // AFTER_PASTE is fired.
  clock.tick(goog.events.PasteHandler.PASTE_POLLING_PERIOD_MS_);
  textarea.value = 'text';
  clock.tick(goog.events.PasteHandler.PASTE_POLLING_PERIOD_MS_);
  assertTrue(afterPasteFired);
}


function testAfterPasteNotFiredIfDelayTooLong() {
  goog.userAgent.IE = true;
  var handlerThatSupportsPasteEvents =
      new goog.events.PasteHandler(textarea);
  pasted = false;
  goog.events.listen(handlerThatSupportsPasteEvents,
      goog.events.PasteHandler.EventType.PASTE,
      function() {
        pasted = true;
      });
  var afterPasteFired = false;
  goog.events.listen(handlerThatSupportsPasteEvents,
      goog.events.PasteHandler.EventType.AFTER_PASTE,
      function() {
        afterPasteFired = true;
      });

  // Initial paste event comes before AFTER_PASTE has fired.
  textarea.dispatchEvent(newBrowserEvent('paste'));
  assertTrue(pasted);
  assertFalse(afterPasteFired);

  // If the new text doesn't show up in time, we never fire AFTER_PASTE.
  clock.tick(goog.events.PasteHandler.PASTE_POLLING_TIMEOUT_MS_);
  textarea.value = 'text';
  clock.tick(goog.events.PasteHandler.PASTE_POLLING_PERIOD_MS_);
  assertFalse(afterPasteFired);
}
