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

goog.module('goog.dom.selectionTest');
goog.setTestOnly();

const InputType = goog.require('goog.dom.InputType');
const TagName = goog.require('goog.dom.TagName');
const dom = goog.require('goog.dom');
const selection = goog.require('goog.dom.selection');
const testSuite = goog.require('goog.testing.testSuite');
const userAgent = goog.require('goog.userAgent');

let input;
let hiddenInput;
let textarea;
let hiddenTextarea;

function getStartHelper(field, hiddenField) {
  assertEquals(0, selection.getStart(field));
  assertEquals(0, selection.getStart(hiddenField));

  field.focus();
  assertEquals(0, selection.getStart(field));
}

function setTextHelper(field) {
  // Test one line string only
  select(field);
  assertEquals('', selection.getText(field));

  selection.setText(field, 'Get Behind Me Satan');
  assertEquals('Get Behind Me Satan', selection.getText(field));
}

function setCursorHelper(field) {
  select(field);
  // try to set the cursor beyond the length of the content
  selection.setStart(field, 5);
  selection.setEnd(field, 15);
  assertEquals(0, selection.getStart(field));
  assertEquals(0, selection.getEnd(field));

  select(field);
  const message = 'Get Behind Me Satan';
  selection.setText(field, message);
  selection.setStart(field, 5);
  selection.setEnd(field, message.length);
  assertEquals(5, selection.getStart(field));
  assertEquals(message.length, selection.getEnd(field));

  // Set the end before the start, and see if getEnd returns the start
  // position itself.
  selection.setStart(field, 5);
  selection.setEnd(field, 3);
  assertEquals(3, selection.getEnd(field));
}

function getAndSetSelectedTextHelper(field) {
  select(field);
  selection.setText(field, 'Get Behind Me Satan');

  // select 'Behind'
  selection.setStart(field, 4);
  selection.setEnd(field, 10);
  assertEquals('Behind', selection.getText(field));

  selection.setText(field, 'In Front Of');
  selection.setStart(field, 0);
  selection.setEnd(field, 100);
  assertEquals('Get In Front Of Me Satan', selection.getText(field));
}

function setCursorOnHiddenInputHelper(hiddenField) {
  selection.setStart(hiddenField, 0);
  assertEquals(0, selection.getStart(hiddenField));
}

/** Helper function to clear the textfield contents. */
function clearField(field) {
  field.value = '';
}

/** Helper function to set the start and end and assert the getter values. */
function checkSetAndGetTextarea(start, end) {
  selection.setStart(textarea, start);
  selection.setEnd(textarea, end);
  assertEquals(start, selection.getStart(textarea));
  assertEquals(end, selection.getEnd(textarea));
}

/**
 * Helper function to focus and select a field. In IE8, selected
 * fields need focus.
 */
function select(field) {
  field.focus();
  field.select();
}
testSuite({
  setUp() {
    input = dom.createDom(TagName.INPUT, {type: InputType.TEXT});
    textarea = dom.createDom(TagName.TEXTAREA);
    hiddenInput = dom.createDom(
        TagName.INPUT, {type: InputType.TEXT, style: 'display: none'});
    hiddenTextarea = dom.createDom(TagName.TEXTAREA, {style: 'display: none'});

    document.body.appendChild(input);
    document.body.appendChild(textarea);
    document.body.appendChild(hiddenInput);
    document.body.appendChild(hiddenTextarea);
  },

  tearDown() {
    dom.removeChildren(document.body);
  },

  /** Tests getStart routine in both input and textarea. */
  testGetStartInput() {
    getStartHelper(input, hiddenInput);
  },

  testGetStartTextarea() {
    getStartHelper(textarea, hiddenTextarea);
  },

  /**
   * Tests that getStart routine does not error for elements that are neither
   * text inputs nor text areas.
   */
  testGetStartOther() {
    const button = dom.createDom(TagName.BUTTON);
    const hiddenButton =
        dom.createDom(TagName.BUTTON, {style: 'display: none'});
    document.body.appendChild(button);
    document.body.appendChild(hiddenButton);
    getStartHelper(button, hiddenButton);
  },

  /**
   * Tests the setText routine for both input and textarea
   * with a single line of text.
   */
  testSetTextInput() {
    setTextHelper(input);
  },

  testSetTextTextarea() {
    setTextHelper(textarea);
  },

  /** Tests the setText routine for textarea with multiple lines of text. */
  testSetTextMultipleLines() {
    select(textarea);
    assertEquals('', selection.getText(textarea));
    const message = 'Get Behind Me\nSatan';
    selection.setText(textarea, message);
    assertEquals(message, selection.getText(textarea));

    // Select the text up to the point just after the \r\n combination
    // or \n in GECKO.
    const endOfNewline = 14;
    let selectedMessage = message.substring(0, endOfNewline);
    selection.setStart(textarea, 0);
    selection.setEnd(textarea, endOfNewline);
    assertEquals(selectedMessage, selection.getText(textarea));

    selectedMessage = '\n';
    selection.setStart(textarea, 13);
    selection.setEnd(textarea, endOfNewline);
    assertEquals(selectedMessage, selection.getText(textarea));
  },

  /** Tests the setCursor routine for both input and textarea. */
  testSetCursorInput() {
    setCursorHelper(input);
  },

  testSetCursorTextarea() {
    setCursorHelper(textarea);
  },

  /**
   * Tests the getText and setText routines acting on selected text in
   * both input and textarea.
   */
  testGetAndSetSelectedTextInput() {
    getAndSetSelectedTextHelper(input);
  },

  testGetAndSetSelectedTextTextarea() {
    getAndSetSelectedTextHelper(textarea);
  },

  /** Test setStart on hidden input and hidden textarea. */
  testSetCursorOnHiddenInput() {
    setCursorOnHiddenInputHelper(hiddenInput);
  },

  testSetCursorOnHiddenTextarea() {
    setCursorOnHiddenInputHelper(hiddenTextarea);
  },

  /**
   * Test setStart, setEnd, getStart and getEnd in textarea with text
   * containing line breaks.
   */
  testSetAndGetCursorWithLineBreaks() {
    select(textarea);
    const newline = '\n';
    let message = `Hello${newline}World`;
    selection.setText(textarea, message);

    // Test setEnd and getEnd, by setting the cursor somewhere after the
    // \r\n combination.
    selection.setEnd(textarea, 9);
    assertEquals(9, selection.getEnd(textarea));

    // Test basic setStart and getStart
    selection.setStart(textarea, 10);
    assertEquals(10, selection.getStart(textarea));

    // Test setEnd and getEnd, by setting the cursor exactly after the
    // \r\n combination in IE or after \n in GECKO.
    let endOfNewline = 6;
    checkSetAndGetTextarea(endOfNewline, endOfNewline);

    // Select a \r\n combination in IE or \n in GECKO and see if
    // getStart and getEnd work correctly.
    clearField(textarea);
    message = `Hello${newline}${newline}World`;
    selection.setText(textarea, message);
    const startOfNewline = 6;
    endOfNewline = 7;
    checkSetAndGetTextarea(startOfNewline, endOfNewline);

    // Select 2 \r\n combinations in IE or 2 \ns in GECKO and see if getStart
    // and getEnd work correctly.
    checkSetAndGetTextarea(5, endOfNewline);

    // Position cursor b/w 2 \r\n combinations in IE or 2 \ns in GECKO and see
    // if getStart and getEnd work correctly.
    clearField(textarea);
    message = `Hello${newline}${newline}${newline}${newline}World`;
    selection.setText(textarea, message);
    const middleOfNewlines = 7;
    checkSetAndGetTextarea(middleOfNewlines, middleOfNewlines);

    // Position cursor at end of a textarea which ends with \r\n in IE or \n in
    // GECKO.
    if (!userAgent.IE) {
      // TODO(johnlenz): investigate why this fails in IE 11.
      clearField(textarea);
      message = `Hello${newline}${newline}`;
      selection.setText(textarea, message);
      const endOfTextarea = message.length;
      checkSetAndGetTextarea(endOfTextarea, endOfTextarea);
    }

    // Position cursor at the end of the 2 starting \r\ns in IE or \ns in GECKO
    // within a textarea.
    clearField(textarea);
    message = `${newline}${newline}World`;
    selection.setText(textarea, message);
    const endOfTwoNewlines = 2;
    checkSetAndGetTextarea(endOfTwoNewlines, endOfTwoNewlines);

    // Position cursor at the end of the first \r\n in IE or \n in
    // GECKO within a textarea.
    let endOfOneNewline = 1;
    checkSetAndGetTextarea(endOfOneNewline, endOfOneNewline);
  },

  /**
   * Test to make sure there's no error when getting the range of an unselected
   * textarea. See bug 1274027.
   */
  testGetStartOnUnfocusedTextarea() {
    input.value = 'White Blood Cells';
    input.focus();
    selection.setCursorPosition(input, 5);

    assertEquals(
        'getStart on input should return where we put the cursor', 5,
        selection.getStart(input));

    assertEquals(
        'getStart on unfocused textarea should succeed without error', 0,
        selection.getStart(textarea));
  },

  /**
   * Test to make sure there's no error setting cursor position within a
   * textarea after a newline. This is problematic on IE because of the
   * '\r\n' vs '\n' issue.
   */
  testSetCursorPositionTextareaWithNewlines() {
    textarea.value = 'Hello\nWorld';
    textarea.focus();

    // Set the selection point between 'W' and 'o'. Position is computed this
    // way instead of being hard-coded because it's different in IE due to \r\n
    // vs \n.
    selection.setCursorPosition(textarea, textarea.value.length - 4);

    const expectedLeftString = 'Hello\nW';

    assertEquals(
        'getStart on input should return after the newline',
        expectedLeftString.length, selection.getStart(textarea));
    assertEquals(
        'getEnd on input should return after the newline',
        expectedLeftString.length, selection.getEnd(textarea));

    selection.setEnd(textarea, textarea.value.length);
    assertEquals('orld', selection.getText(textarea));
  },
});
