textrange_test.js

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

goog.require('goog.dom');
goog.require('goog.dom.ControlRange');
goog.require('goog.dom.Range');
goog.require('goog.dom.TextRange');
goog.require('goog.math.Coordinate');
goog.require('goog.style');
goog.require('goog.testing.ExpectedFailures');
goog.require('goog.testing.jsunit');
goog.require('goog.userAgent');

var logo;
var logo2;
var logo3;
var logo3Rtl;
var table;
var table2;
var table2div;
var test3;
var test3Rtl;
var expectedFailures;

function setUpPage() {
  logo = goog.dom.getElement('logo');
  logo2 = goog.dom.getElement('logo2');
  logo3 = goog.dom.getElement('logo3');
  logo3Rtl = goog.dom.getElement('logo3Rtl');
  table = goog.dom.getElement('table');
  table2 = goog.dom.getElement('table2');
  table2div = goog.dom.getElement('table2div');
  test3 = goog.dom.getElement('test3');
  test3Rtl = goog.dom.getElement('test3Rtl');
  expectedFailures = new goog.testing.ExpectedFailures();
}

function tearDown() {
  expectedFailures.handleTearDown();
}

function testCreateFromNodeContents() {
  assertNotNull('Text range object can be created for element node',
      goog.dom.TextRange.createFromNodeContents(logo));
  assertNotNull('Text range object can be created for text node',
      goog.dom.TextRange.createFromNodeContents(logo2.previousSibling));
}

function testMoveToNodes() {
  var range = goog.dom.TextRange.createFromNodeContents(table2);
  range.moveToNodes(table2div, 0, table2div, 1, false);
  assertEquals('Range should start in table2div',
               table2div,
               range.getStartNode());
  assertEquals('Range should end in table2div',
               table2div,
               range.getEndNode());
  assertEquals('Range start offset should be 0',
               0,
               range.getStartOffset());
  assertEquals('Range end offset should be 0',
               1,
               range.getEndOffset());
  assertFalse('Range should not be reversed',
              range.isReversed());
  range.moveToNodes(table2div, 0, table2div, 1, true);
  assertTrue('Range should be reversed',
             range.isReversed());
  assertEquals('Range text should be "foo"',
               'foo',
               range.getText());
}

function testContainsTextRange() {
  var range = goog.dom.TextRange.createFromNodeContents(table2);
  var range2 = goog.dom.TextRange.createFromNodeContents(table2div);
  assertTrue('TextRange contains other TextRange',
      range.containsRange(range2));
  assertFalse('TextRange does not contain other TextRange',
      range2.containsRange(range));

  range = goog.dom.Range.createFromNodes(
      table2div.firstChild, 1, table2div.lastChild, 1);
  range2 = goog.dom.TextRange.createFromNodes(
      table2div.firstChild, 0, table2div.lastChild, 0);
  assertTrue('TextRange partially contains other TextRange',
      range2.containsRange(range, true));
  assertFalse('TextRange does not fully contain other TextRange',
      range2.containsRange(range, false));
}

function testContainsControlRange() {
  if (goog.userAgent.IE) {
    var range = goog.dom.ControlRange.createFromElements(table2);
    var range2 = goog.dom.TextRange.createFromNodeContents(table2div);
    assertFalse('TextRange does not contain ControlRange',
        range2.containsRange(range));
    range = goog.dom.ControlRange.createFromElements(logo2);
    assertTrue('TextRange contains ControlRange',
        range2.containsRange(range));
    range = goog.dom.TextRange.createFromNodeContents(table2);
    range2 = goog.dom.ControlRange.createFromElements(logo, logo2);
    assertTrue('TextRange partially contains ControlRange',
        range2.containsRange(range, true));
    assertFalse('TextRange does not fully contain ControlRange',
        range2.containsRange(range, false));
  }
}

function testGetStartPosition() {
  expectedFailures.expectFailureFor(goog.userAgent.GECKO &&
      !goog.userAgent.isVersionOrHigher('2'));

  // The start node is in the top left.
  var range = goog.dom.TextRange.createFromNodeContents(test3);
  var topLeft = goog.style.getPageOffset(test3.firstChild);

  if (goog.userAgent.IE) {
    // On IE the selection is as tall as its tallest element.
    var logoPosition = goog.style.getPageOffset(logo3);
    topLeft.y = logoPosition.y;

    if (!goog.userAgent.isVersionOrHigher('8')) {
      topLeft.x += 2;
      topLeft.y += 2;
    }
  }

  try {
    var result = assertNotThrows(goog.bind(range.getStartPosition, range));
    assertObjectEquals(topLeft, result);
  } catch (e) {
    expectedFailures.handleException(e);
  }
}

function testGetStartPositionNotInDocument() {
  expectedFailures.expectFailureFor(goog.userAgent.GECKO &&
      !goog.userAgent.isVersionOrHigher('2'));
  expectedFailures.expectFailureFor(goog.userAgent.IE &&
      !goog.userAgent.isVersionOrHigher('8'));

  var range = goog.dom.TextRange.createFromNodeContents(test3);

  goog.dom.removeNode(test3);
  try {
    var result = assertNotThrows(goog.bind(range.getStartPosition, range));
    assertNull(result);
  } catch (e) {
    expectedFailures.handleException(e);
  } finally {
    goog.dom.appendChild(document.body, test3);
  }
}

function testGetStartPositionReversed() {
  expectedFailures.expectFailureFor(goog.userAgent.GECKO &&
      !goog.userAgent.isVersionOrHigher('2'));

  // Simulate the user selecting backwards from right-to-left.
  // The start node is now in the bottom right.
  var firstNode = test3.firstChild.firstChild;
  var lastNode = test3.lastChild.lastChild;
  var range = goog.dom.TextRange.createFromNodes(
      lastNode, lastNode.nodeValue.length, firstNode, 0);
  var pageOffset = goog.style.getPageOffset(test3.lastChild);
  var bottomRight = new goog.math.Coordinate(
      pageOffset.x + test3.lastChild.offsetWidth,
      pageOffset.y + test3.lastChild.offsetHeight);

  if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
    bottomRight.x += 2;
    bottomRight.y += 2;
  }

  try {
    var result = assertNotThrows(goog.bind(range.getStartPosition, range));
    assertObjectRoughlyEquals(bottomRight, result, 1);
  } catch (e) {
    expectedFailures.handleException(e);
  }
}

function testGetStartPositionRightToLeft() {
  expectedFailures.expectFailureFor(goog.userAgent.GECKO &&
      !goog.userAgent.isVersionOrHigher('2'));

  // Even in RTL content the start node is still in the top left.
  var range = goog.dom.TextRange.createFromNodeContents(test3Rtl);
  var topLeft = goog.style.getPageOffset(test3Rtl.firstChild);

  if (goog.userAgent.IE) {
    // On IE the selection is as tall as its tallest element.
    var logoPosition = goog.style.getPageOffset(logo3Rtl);
    topLeft.y = logoPosition.y;

    if (!goog.userAgent.isVersionOrHigher('8')) {
      topLeft.x += 2;
      topLeft.y += 2;
    }
  }

  try {
    var result = assertNotThrows(goog.bind(range.getStartPosition, range));
    assertObjectEquals(topLeft, result);
  } catch (e) {
    expectedFailures.handleException(e);
  }
}

function testGetEndPosition() {
  expectedFailures.expectFailureFor(goog.userAgent.GECKO &&
      !goog.userAgent.isVersionOrHigher('2'));

  // The end node is in the bottom right.
  var range = goog.dom.TextRange.createFromNodeContents(test3);
  var pageOffset = goog.style.getPageOffset(test3.lastChild);
  var bottomRight = new goog.math.Coordinate(
      pageOffset.x + test3.lastChild.offsetWidth,
      pageOffset.y + test3.lastChild.offsetHeight);

  if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
    bottomRight.x += 6;
    bottomRight.y += 2;
  }

  try {
    var result = assertNotThrows(goog.bind(range.getEndPosition, range));
    assertObjectRoughlyEquals(bottomRight, result, 1);
  } catch (e) {
    expectedFailures.handleException(e);
  }
}

function testGetEndPositionNotInDocument() {
  expectedFailures.expectFailureFor(goog.userAgent.GECKO &&
      !goog.userAgent.isVersionOrHigher('2'));
  expectedFailures.expectFailureFor(goog.userAgent.IE &&
      !goog.userAgent.isVersionOrHigher('8'));

  var range = goog.dom.TextRange.createFromNodeContents(test3);

  goog.dom.removeNode(test3);
  try {
    var result = assertNotThrows(goog.bind(range.getEndPosition, range));
    assertNull(result);
  } catch (e) {
    expectedFailures.handleException(e);
  } finally {
    goog.dom.appendChild(document.body, test3);
  }
}

function testGetEndPositionReversed() {
  expectedFailures.expectFailureFor(goog.userAgent.GECKO &&
      !goog.userAgent.isVersionOrHigher('2'));

  // Simulate the user selecting backwards from right-to-left.
  // The end node is now in the top left.
  var firstNode = test3.firstChild.firstChild;
  var lastNode = test3.lastChild.lastChild;
  var range = goog.dom.TextRange.createFromNodes(
      lastNode, lastNode.nodeValue.length, firstNode, 0);
  var topLeft = goog.style.getPageOffset(test3.firstChild);

  if (goog.userAgent.IE) {
    // On IE the selection is as tall as its tallest element.
    var logoPosition = goog.style.getPageOffset(logo3);
    topLeft.y = logoPosition.y;

    if (!goog.userAgent.isVersionOrHigher('8')) {
      topLeft.x += 2;
      topLeft.y += 2;
    }
  }

  try {
    var result = assertNotThrows(goog.bind(range.getEndPosition, range));
    assertObjectEquals(topLeft, result);
  } catch (e) {
    expectedFailures.handleException(e);
  }
}

function testGetEndPositionRightToLeft() {
  expectedFailures.expectFailureFor(goog.userAgent.GECKO &&
      !goog.userAgent.isVersionOrHigher('2'));
  expectedFailures.expectFailureFor(goog.userAgent.IE &&
      !goog.userAgent.isVersionOrHigher('8'));

  // Even in RTL content the end node is still in the bottom right.
  var range = goog.dom.TextRange.createFromNodeContents(test3Rtl);
  var pageOffset = goog.style.getPageOffset(test3Rtl.lastChild);
  var bottomRight = new goog.math.Coordinate(
      pageOffset.x + test3Rtl.lastChild.offsetWidth,
      pageOffset.y + test3Rtl.lastChild.offsetHeight);

  if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
    bottomRight.x += 2;
    bottomRight.y += 2;
  }

  try {
    var result = assertNotThrows(goog.bind(range.getEndPosition, range));
    assertObjectRoughlyEquals(bottomRight, result, 1);
  } catch (e) {
    expectedFailures.handleException(e);
  }
}

function testCloneRangeDeep() {
  var range = goog.dom.TextRange.createFromNodeContents(logo);
  assertFalse(range.isCollapsed());

  var cloned = range.clone();
  cloned.collapse();
  assertTrue(cloned.isCollapsed());
  assertFalse(range.isCollapsed());
}