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

/**
 * @fileoverview Unit tests for goog.position.
 */

/** @suppress {extraProvide} */
goog.module('goog.positioningTest');
goog.setTestOnly();

const Box = goog.require('goog.math.Box');
const Coordinate = goog.require('goog.math.Coordinate');
const Corner = goog.require('goog.positioning.Corner');
const DomHelper = goog.require('goog.dom.DomHelper');
const ExpectedFailures = goog.require('goog.testing.ExpectedFailures');
const Overflow = goog.require('goog.positioning.Overflow');
const OverflowStatus = goog.require('goog.positioning.OverflowStatus');
const Size = goog.require('goog.math.Size');
const TagName = goog.require('goog.dom.TagName');
const browser = goog.require('goog.labs.userAgent.browser');
const dom = goog.require('goog.dom');
const positioning = goog.require('goog.positioning');
const product = goog.require('goog.userAgent.product');
const style = goog.require('goog.style');
const testSuite = goog.require('goog.testing.testSuite');
const userAgent = goog.require('goog.userAgent');

// Allow positions to be off by one in gecko as it reports scrolling
// offsets in steps of 2.  Otherwise, allow for subpixel difference
// as seen in IE10+
const ALLOWED_OFFSET = userAgent.GECKO ? 1 : 0.1;
// Error bar for positions since some browsers are not super accurate
// in reporting them.
const EPSILON = 2;

let expectedFailures;

const corner = Corner;
const overflow = Overflow;
let testArea;

/** This is used to round pixel values on FF3 Mac. */
function assertRoundedEquals(a, b, c) {
  if (arguments.length == 3) {
    assertRoughlyEquals(a, b, c, ALLOWED_OFFSET);
  } else {
    assertRoughlyEquals(a, b, ALLOWED_OFFSET);
  }
}

function createPopupDiv(width, height) {
  const popupDiv = dom.createElement(TagName.DIV);
  popupDiv.style.position = 'absolute';
  style.setSize(popupDiv, width, height);
  style.setPosition(popupDiv, 0 /* left */, 250 /* top */);
  return popupDiv;
}

function newCoord(x, y) {
  return new Coordinate(x, y);
}

function newSize(w, h) {
  return new Size(w, h);
}

function newBox(coord, size) {}
testSuite({
  setUpPage() {
    expectedFailures = new ExpectedFailures();
  },

  setUp() {
    window.scrollTo(0, 0);

    const viewportSize = dom.getViewportSize();
    // Some tests need enough size viewport.
    if (viewportSize.width < 600 || viewportSize.height < 600) {
      window.moveTo(0, 0);
      window.resizeTo(640, 640);
    }

    testArea = dom.getElement('test-area');
  },

  tearDown() {
    expectedFailures.handleTearDown();
    testArea.setAttribute('style', '');
    dom.removeChildren(testArea);
  },

  testPositionAtAnchorLeftToRight() {
    const anchor = document.getElementById('anchor1');
    const popup = document.getElementById('popup1');

    // Anchor top left to top left.
    positioning.positionAtAnchor(
        anchor, corner.TOP_LEFT, popup, corner.TOP_LEFT);
    let anchorRect = style.getBounds(anchor);
    let popupRect = style.getBounds(popup);
    assertRoundedEquals(
        'Left edge of popup should line up with left edge ' +
            'of anchor.',
        anchorRect.left, popupRect.left);
    assertRoundedEquals(
        'Popup should have the same y position as the anchor.', anchorRect.top,
        popupRect.top);

    // Anchor top left to bottom left.
    positioning.positionAtAnchor(
        anchor, corner.BOTTOM_LEFT, popup, corner.TOP_LEFT);
    anchorRect = style.getBounds(anchor);
    popupRect = style.getBounds(popup);
    assertRoundedEquals(
        'Left edge of popup should line up with left edge ' +
            'of anchor.',
        anchorRect.left, popupRect.left);
    assertRoundedEquals(
        'Popup should be positioned just below the anchor.',
        anchorRect.top + anchorRect.height, popupRect.top);

    // Anchor top left to top right.
    positioning.positionAtAnchor(
        anchor, corner.TOP_RIGHT, popup, corner.TOP_LEFT);
    anchorRect = style.getBounds(anchor);
    popupRect = style.getBounds(popup);
    assertRoundedEquals(
        'Popup should be positioned just right of the anchor.',
        anchorRect.left + anchorRect.width, popupRect.left);
    assertRoundedEquals(
        'Popup should have the same y position as the anchor.', anchorRect.top,
        popupRect.top);

    // Anchor top right to bottom right.
    positioning.positionAtAnchor(
        anchor, corner.BOTTOM_RIGHT, popup, corner.TOP_RIGHT);
    anchorRect = style.getBounds(anchor);
    popupRect = style.getBounds(popup);
    assertRoundedEquals(
        'Right edge of popup should line up with right edge ' +
            'of anchor.',
        anchorRect.left + anchorRect.width, popupRect.left + popupRect.width);
    assertRoundedEquals(
        'Popup should be positioned just below the anchor.',
        anchorRect.top + anchorRect.height, popupRect.top);

    // Anchor top start to bottom start.
    positioning.positionAtAnchor(
        anchor, corner.BOTTOM_START, popup, corner.TOP_START);
    anchorRect = style.getBounds(anchor);
    popupRect = style.getBounds(popup);
    assertRoundedEquals(
        'Left edge of popup should line up with left edge ' +
            'of anchor.',
        anchorRect.left, popupRect.left);
    assertRoundedEquals(
        'Popup should be positioned just below the anchor.',
        anchorRect.top + anchorRect.height, popupRect.top);

    // Anchor top center to top center.
    positioning.positionAtAnchor(
        anchor, corner.TOP_CENTER, popup, corner.TOP_CENTER);
    anchorRect = style.getBounds(anchor);
    popupRect = style.getBounds(popup);
    // We use flooring math because we consider 10 and 10.5 to be "equal".
    // IE8 doesn't allow split pixels in positioning and as such this test would
    // fail on it, as Anchor L+W/2 is round while Popup L+W/2 is .5 away.
    assertRoundedEquals(
        'The center of popup should line up with the center ' +
            'of anchor.',
        Math.floor(anchorRect.left + anchorRect.width / 2),
        Math.floor(popupRect.left + popupRect.width / 2));
    assertRoundedEquals(
        'Popup should have the same y position as the anchor.', anchorRect.top,
        popupRect.top);

    // Anchor top center to top left.
    positioning.positionAtAnchor(
        anchor, corner.TOP_CENTER, popup, corner.TOP_LEFT);
    anchorRect = style.getBounds(anchor);
    popupRect = style.getBounds(popup);
    assertRoundedEquals(
        'Left edge of popup should line up with the center ' +
            'of anchor.',
        Math.floor(anchorRect.left + anchorRect.width / 2),
        Math.floor(popupRect.left));
    assertRoundedEquals(
        'Popup should have the same y position as the anchor.', anchorRect.top,
        popupRect.top);

    // Anchor bottom center to top left.
    positioning.positionAtAnchor(
        anchor, corner.BOTTOM_CENTER, popup, corner.TOP_LEFT);
    anchorRect = style.getBounds(anchor);
    popupRect = style.getBounds(popup);
    assertRoundedEquals(
        'Left edge of popup should line up with the middle ' +
            'of anchor.',
        Math.floor(anchorRect.left + anchorRect.width / 2),
        Math.floor(popupRect.left));
    assertRoundedEquals(
        'Popup should be positioned just below the anchor.',
        anchorRect.top + anchorRect.height, popupRect.top);

    // Anchor bottom left to top center.
    positioning.positionAtAnchor(
        anchor, corner.BOTTOM_LEFT, popup, corner.TOP_CENTER);
    anchorRect = style.getBounds(anchor);
    popupRect = style.getBounds(popup);
    assertRoundedEquals(
        'Left edge of popup should line up with the middle ' +
            'of anchor.',
        anchorRect.left, Math.floor(popupRect.left + popupRect.width / 2));
    assertRoundedEquals(
        'Popup should be positioned just below the anchor.',
        anchorRect.top + anchorRect.height, popupRect.top);
  },

  testPositionAtAnchorWithOffset() {
    const anchor = document.getElementById('anchor1');
    const popup = document.getElementById('popup1');

    // Anchor top left to top left with an offset moving the popup away from the
    // anchor.
    positioning.positionAtAnchor(
        anchor, corner.TOP_LEFT, popup, corner.TOP_LEFT, newCoord(-15, -20));
    let anchorRect = style.getBounds(anchor);
    let popupRect = style.getBounds(popup);
    assertRoundedEquals(
        'Left edge of popup should be fifteen pixels from ' +
            'anchor.',
        anchorRect.left, popupRect.left + 15);
    assertRoundedEquals(
        'Top edge of popup should be twenty pixels from anchor.',
        anchorRect.top, popupRect.top + 20);

    // Anchor top left to top left with an offset moving the popup towards the
    // anchor.
    positioning.positionAtAnchor(
        anchor, corner.TOP_LEFT, popup, corner.TOP_LEFT, newCoord(3, 1));
    anchorRect = style.getBounds(anchor);
    popupRect = style.getBounds(popup);
    assertRoundedEquals(
        'Left edge of popup should be three pixels right of ' +
            'the anchor\'s left edge',
        anchorRect.left, popupRect.left - 3);
    assertRoundedEquals(
        'Top edge of popup should be one pixel below of the ' +
            'anchor\'s top edge',
        anchorRect.top, popupRect.top - 1);
  },

  /**
     @suppress {strictPrimitiveOperators} suppression added to enable type
     checking
   */
  testPositionAtAnchorOverflowLeftEdgeRightToLeft() {
    const anchor = document.getElementById('anchor5');
    const popup = document.getElementById('popup5');

    let status = positioning.positionAtAnchor(
        anchor, corner.TOP_LEFT, popup, corner.TOP_RIGHT, undefined, undefined,
        overflow.FAIL_X);
    assertFalse(
        'Positioning operation should have failed.',
        (status & OverflowStatus.FAILED) == 0);

    // Change overflow strategy to ADJUST.
    status = positioning.positionAtAnchor(
        anchor, corner.TOP_LEFT, popup, corner.TOP_RIGHT, undefined, undefined,
        overflow.ADJUST_X);

    // Fails in Chrome because of infrastructure issues, temporarily disabled.
    // See b/4274723.
    expectedFailures.expectFailureFor(product.CHROME);
    try {
      assertTrue(
          'Positioning operation should have been successful.',
          (status & OverflowStatus.FAILED) == 0);
      assertTrue(
          'Positioning operation should have been adjusted.',
          (status & OverflowStatus.ADJUSTED_X) != 0);
    } catch (e) {
      expectedFailures.handleException(e);
    }

    const anchorRect = style.getBounds(anchor);
    const popupRect = style.getBounds(popup);
    /** @suppress {checkTypes} suppression added to enable type checking */
    const parentRect = style.getBounds(anchor.parentNode);
    assertTrue(
        'Position should have been adjusted so that the left edge of ' +
            'the popup is left of the anchor but still within the bounding ' +
            'box of the parent container.',
        anchorRect.left <= popupRect.left <= parentRect.left);
  },

  testPositionAtAnchorWithMargin() {
    const anchor = document.getElementById('anchor1');
    const popup = document.getElementById('popup1');

    // Anchor top left to top left.
    positioning.positionAtAnchor(
        anchor, corner.TOP_LEFT, popup, corner.TOP_LEFT, undefined,
        new Box(1, 2, 3, 4));
    let anchorRect = style.getBounds(anchor);
    let popupRect = style.getBounds(popup);
    assertRoundedEquals(
        'Left edge of popup should be four pixels from anchor.',
        anchorRect.left, popupRect.left - 4);
    assertRoundedEquals(
        'Top edge of popup should be one pixels from anchor.', anchorRect.top,
        popupRect.top - 1);

    // Anchor top right to bottom right.
    positioning.positionAtAnchor(
        anchor, corner.BOTTOM_RIGHT, popup, corner.TOP_RIGHT, undefined,
        new Box(1, 2, 3, 4));
    anchorRect = style.getBounds(anchor);
    popupRect = style.getBounds(popup);

    /** @suppress {visibility} suppression added to enable type checking */
    const visibleAnchorRect = positioning.getVisiblePart_(anchor);

    assertRoundedEquals(
        'Right edge of popup should line up with right edge ' +
            'of anchor.',
        visibleAnchorRect.left + visibleAnchorRect.width,
        popupRect.left + popupRect.width + 2);

    assertRoundedEquals(
        'Popup should be positioned just below the anchor.',
        visibleAnchorRect.top + visibleAnchorRect.height, popupRect.top - 1);
  },

  testPositionAtAnchorRightToLeft() {
    if (userAgent.IE) {
      // These tests fails with IE6.
      // TODO(user): Investigate the reason.
      return;
    }
    if (product.SAFARI) {
      // TODO(user): Disabled so we can get the rest of the Closure test
      // suite running in a continuous build. Will investigate later.
      return;
    }

    const anchor = document.getElementById('anchor2');
    const popup = document.getElementById('popup2');

    // Anchor top left to top left.
    positioning.positionAtAnchor(
        anchor, corner.TOP_LEFT, popup, corner.TOP_LEFT);
    let anchorRect = style.getBounds(anchor);
    let popupRect = style.getBounds(popup);

    assertRoundedEquals(
        'Left edge of popup should line up with left edge ' +
            'of anchor.',
        anchorRect.left, popupRect.left);
    assertRoundedEquals(
        'Popup should have the same y position as the anchor.', anchorRect.top,
        popupRect.top);

    // Anchor top start to bottom start.
    positioning.positionAtAnchor(
        anchor, corner.BOTTOM_START, popup, corner.TOP_START);
    anchorRect = style.getBounds(anchor);
    popupRect = style.getBounds(popup);
    assertRoundedEquals(
        'Right edge of popup should line up with right edge ' +
            'of anchor.',
        anchorRect.left + anchorRect.width, popupRect.left + popupRect.width);
    assertRoundedEquals(
        'Popup should be positioned just below the anchor.',
        anchorRect.top + anchorRect.height, popupRect.top);
  },

  testPositionAtAnchorRightToLeftWithScroll() {
    if (userAgent.IE) {
      // These tests fails with IE6.
      // TODO(user): Investigate the reason.
      return;
    }
    if (product.SAFARI) {
      // TODO(user): Disabled so we can get the rest of the Closure test
      // suite running in a continuous build. Will investigate later.
      return;
    }

    const anchor = document.getElementById('anchor8');
    const popup = document.getElementById('popup8');

    // Anchor top left to top left.
    positioning.positionAtAnchor(
        anchor, corner.TOP_LEFT, popup, corner.TOP_LEFT);
    let anchorRect = style.getBounds(anchor);
    let popupRect = style.getBounds(popup);

    // TODO(joeltine): Chrome 47 has issues with RTL scroll positioning. Remove
    // chrome check when
    // https://code.google.com/p/chromium/issues/detail?id=568706 is resolved.
    if (!browser.isChrome()) {
      assertRoundedEquals(
          'Left edge of popup should line up with left edge ' +
              'of anchor.',
          anchorRect.left, popupRect.left);
    }
    assertRoundedEquals(
        'Popup should have the same y position as the anchor.', anchorRect.top,
        popupRect.top);

    // Anchor top start to bottom start.
    positioning.positionAtAnchor(
        anchor, corner.BOTTOM_START, popup, corner.TOP_START);
    anchorRect = style.getBounds(anchor);
    popupRect = style.getBounds(popup);

    /** @suppress {visibility} suppression added to enable type checking */
    const visibleAnchorRect = positioning.getVisiblePart_(anchor);
    const visibleAnchorBox = visibleAnchorRect.toBox();

    // TODO(joeltine): Chrome 47 has issues with RTL scroll positioning. Remove
    // chrome check when
    // https://code.google.com/p/chromium/issues/detail?id=568706 is resolved.
    if (!browser.isChrome()) {
      assertRoundedEquals(
          'Right edge of popup should line up with right edge ' +
              'of anchor.',
          anchorRect.left + anchorRect.width, popupRect.left + popupRect.width);
    }
    assertRoundedEquals(
        'Popup should be positioned just below the anchor.',
        visibleAnchorBox.bottom, popupRect.top);
  },

  testPositionAtAnchorBodyViewport() {
    const anchor = document.getElementById('anchor1');
    const popup = document.getElementById('popup3');

    // Anchor top left to top left.
    positioning.positionAtAnchor(
        anchor, corner.TOP_LEFT, popup, corner.TOP_LEFT);
    let anchorRect = style.getBounds(anchor);
    let popupRect = style.getBounds(popup);
    assertEquals(
        'Left edge of popup should line up with left edge of anchor.',
        anchorRect.left, popupRect.left);
    assertRoughlyEquals(
        'Popup should have the same y position as the anchor.', anchorRect.top,
        popupRect.top, 1);

    // Anchor top start to bottom right.
    positioning.positionAtAnchor(
        anchor, corner.BOTTOM_RIGHT, popup, corner.TOP_RIGHT);
    anchorRect = style.getBounds(anchor);
    popupRect = style.getBounds(popup);
    assertEquals(
        'Right edge of popup should line up with right edge of anchor.',
        anchorRect.left + anchorRect.width, popupRect.left + popupRect.width);
    assertRoughlyEquals(
        'Popup should be positioned just below the anchor.',
        anchorRect.top + anchorRect.height, popupRect.top, 1);

    // Anchor top right to top left.
    positioning.positionAtAnchor(
        anchor, corner.TOP_LEFT, popup, corner.TOP_RIGHT);
    anchorRect = style.getBounds(anchor);
    popupRect = style.getBounds(popup);
    assertEquals(
        'Right edge of popup should line up with left edge of anchor.',
        anchorRect.left, popupRect.left + popupRect.width);
    assertRoughlyEquals(
        'Popup should have the same y position as the anchor.', anchorRect.top,
        popupRect.top, 1);
  },

  testPositionAtAnchorSpecificViewport() {
    const anchor = document.getElementById('anchor1');
    const popup = document.getElementById('popup3');

    // Anchor top right to top left within outerbox.
    let status = positioning.positionAtAnchor(
        anchor, corner.TOP_LEFT, popup, corner.TOP_RIGHT, undefined, undefined,
        overflow.FAIL_X);
    let anchorRect = style.getBounds(anchor);
    let popupRect = style.getBounds(popup);
    assertTrue(
        'Positioning operation should have been successful.',
        (status & OverflowStatus.FAILED) == 0);
    assertTrue(
        'X position should not have been adjusted.',
        (status & OverflowStatus.ADJUSTED_X) == 0);
    assertTrue(
        'Y position should not have been adjusted.',
        (status & OverflowStatus.ADJUSTED_Y) == 0);
    assertEquals(
        'Right edge of popup should line up with left edge of anchor.',
        anchorRect.left, popupRect.left + popupRect.width);
    assertRoughlyEquals(
        'Popup should have the same y position as the anchor.', anchorRect.top,
        popupRect.top, 1);

    // position again within box1.
    const box = document.getElementById('box1');
    const viewport = style.getBounds(box);
    /** @suppress {checkTypes} suppression added to enable type checking */
    status = positioning.positionAtAnchor(
        anchor, corner.TOP_LEFT, popup, corner.TOP_RIGHT, undefined, undefined,
        overflow.FAIL_X, undefined, viewport);
    assertFalse(
        'Positioning operation should have failed.',
        (status & OverflowStatus.FAILED) == 0);

    // Change overflow strategy to adjust.
    /** @suppress {checkTypes} suppression added to enable type checking */
    status = positioning.positionAtAnchor(
        anchor, corner.TOP_LEFT, popup, corner.TOP_RIGHT, undefined, undefined,
        overflow.ADJUST_X, undefined, viewport);
    anchorRect = style.getBounds(anchor);
    popupRect = style.getBounds(popup);
    assertTrue(
        'Positioning operation should have been successful.',
        (status & OverflowStatus.FAILED) == 0);
    assertFalse(
        'X position should have been adjusted.',
        (status & OverflowStatus.ADJUSTED_X) == 0);
    assertTrue(
        'Y position should not have been adjusted.',
        (status & OverflowStatus.ADJUSTED_Y) == 0);
    assertRoughlyEquals(
        'Left edge of popup should line up with left edge of viewport.',
        viewport.left, popupRect.left, EPSILON);
    assertRoughlyEquals(
        'Popup should have the same y position as the anchor.', anchorRect.top,
        popupRect.top, 1);
  },

  testPositionAtAnchorOutsideViewport() {
    const anchor = document.getElementById('anchor4');
    const popup = document.getElementById('popup1');

    let status = positioning.positionAtAnchor(
        anchor, corner.TOP_LEFT, popup, corner.TOP_RIGHT);
    let anchorRect = style.getBounds(anchor);
    let popupRect = style.getBounds(popup);
    assertTrue(
        'Positioning operation should have been successful.',
        (status & OverflowStatus.FAILED) == 0);
    assertTrue(
        'X position should not have been adjusted.',
        (status & OverflowStatus.ADJUSTED_X) == 0);
    assertTrue(
        'Y position should not have been adjusted.',
        (status & OverflowStatus.ADJUSTED_Y) == 0);

    assertEquals(
        'Right edge of popup should line up with left edge of anchor.',
        anchorRect.left, popupRect.left + popupRect.width);

    // Change overflow strategy to fail.
    status = positioning.positionAtAnchor(
        anchor, corner.BOTTOM_RIGHT, popup, corner.TOP_RIGHT, undefined,
        undefined, overflow.FAIL_X);
    assertFalse(
        'Positioning operation should have failed.',
        (status & OverflowStatus.FAILED) == 0);

    // Change overflow strategy to adjust.
    status = positioning.positionAtAnchor(
        anchor, corner.BOTTOM_RIGHT, popup, corner.TOP_RIGHT, undefined,
        undefined, overflow.ADJUST_X);
    anchorRect = style.getBounds(anchor);
    popupRect = style.getBounds(popup);
    assertTrue(
        'Positioning operation should have been successful.',
        (status & OverflowStatus.FAILED) == 0);
    assertFalse(
        'X position should have been adjusted.',
        (status & OverflowStatus.ADJUSTED_X) == 0);
    assertTrue(
        'Y position should not have been adjusted.',
        (status & OverflowStatus.ADJUSTED_Y) == 0);
    assertRoughlyEquals(
        'Left edge of popup should line up with left edge of viewport.', 0,
        popupRect.left, EPSILON);
    assertEquals(
        'Popup should be positioned just below the anchor.',
        anchorRect.top + anchorRect.height, popupRect.top);
  },

  testAdjustForViewportFailIgnore() {
    /** @suppress {visibility} suppression added to enable type checking */
    const f = positioning.adjustForViewport_;
    const viewport = new Box(100, 200, 200, 100);
    const overflow = Overflow.IGNORE;

    let pos = newCoord(150, 150);
    let size = newSize(50, 50);
    assertEquals(
        'Viewport overflow should be ignored.', OverflowStatus.NONE,
        f(pos, size, viewport, overflow));

    pos = newCoord(150, 150);
    size = newSize(100, 50);
    assertEquals(
        'Viewport overflow should be ignored.', OverflowStatus.NONE,
        f(pos, size, viewport, overflow));

    pos = newCoord(50, 50);
    size = newSize(50, 50);
    assertEquals(
        'Viewport overflow should be ignored.', OverflowStatus.NONE,
        f(pos, size, viewport, overflow));
  },

  testAdjustForViewportFailXY() {
    /** @suppress {visibility} suppression added to enable type checking */
    const f = positioning.adjustForViewport_;
    const viewport = new Box(100, 200, 200, 100);
    const overflow = Overflow.FAIL_X | Overflow.FAIL_Y;

    let pos = newCoord(150, 150);
    let size = newSize(50, 50);
    assertEquals(
        'Element should not overflow viewport.', OverflowStatus.NONE,
        f(pos, size, viewport, overflow));

    pos = newCoord(150, 150);
    size = newSize(100, 50);
    assertEquals(
        'Element should overflow the right edge of viewport.',
        OverflowStatus.FAILED_RIGHT, f(pos, size, viewport, overflow));

    pos = newCoord(150, 150);
    size = newSize(50, 100);
    assertEquals(
        'Element should overflow the bottom edge of viewport.',
        OverflowStatus.FAILED_BOTTOM, f(pos, size, viewport, overflow));

    pos = newCoord(50, 150);
    size = newSize(50, 50);
    assertEquals(
        'Element should overflow the left edge of viewport.',
        OverflowStatus.FAILED_LEFT, f(pos, size, viewport, overflow));

    pos = newCoord(150, 50);
    size = newSize(50, 50);
    assertEquals(
        'Element should overflow the top edge of viewport.',
        OverflowStatus.FAILED_TOP, f(pos, size, viewport, overflow));

    pos = newCoord(50, 50);
    size = newSize(50, 50);
    assertEquals(
        'Element should overflow the left & top edges of viewport.',
        OverflowStatus.FAILED_LEFT | OverflowStatus.FAILED_TOP,
        f(pos, size, viewport, overflow));
  },

  testAdjustForViewportAdjustXFailY() {
    /** @suppress {visibility} suppression added to enable type checking */
    const f = positioning.adjustForViewport_;
    const viewport = new Box(100, 200, 200, 100);
    const overflow = Overflow.ADJUST_X | Overflow.FAIL_Y;

    let pos = newCoord(150, 150);
    let size = newSize(50, 50);
    assertEquals(
        'Element should not overflow viewport.', OverflowStatus.NONE,
        f(pos, size, viewport, overflow));
    assertEquals('X Position should not have been changed.', 150, pos.x);
    assertEquals('Y Position should not have been changed.', 150, pos.y);

    pos = newCoord(150, 150);
    size = newSize(100, 50);
    assertEquals(
        'Element position should be adjusted not to overflow right ' +
            'edge of viewport.',
        OverflowStatus.ADJUSTED_X, f(pos, size, viewport, overflow));
    assertEquals('X Position should be adjusted to 100.', 100, pos.x);
    assertEquals('Y Position should not have been changed.', 150, pos.y);

    pos = newCoord(50, 150);
    size = newSize(100, 50);
    assertEquals(
        'Element position should be adjusted not to overflow left ' +
            'edge of viewport.',
        OverflowStatus.ADJUSTED_X, f(pos, size, viewport, overflow));
    assertEquals('X Position should be adjusted to 100.', 100, pos.x);
    assertEquals('Y Position should not have been changed.', 150, pos.y);

    pos = newCoord(50, 50);
    size = newSize(100, 50);
    assertEquals(
        'Element position should be adjusted not to overflow left ' +
            'edge of viewport, should overflow bottom edge.',
        OverflowStatus.ADJUSTED_X | OverflowStatus.FAILED_TOP,
        f(pos, size, viewport, overflow));
    assertEquals('X Position should be adjusted to 100.', 100, pos.x);
    assertEquals('Y Position should not have been changed.', 50, pos.y);
  },

  testAdjustForViewportResizeHeight() {
    /** @suppress {visibility} suppression added to enable type checking */
    const f = positioning.adjustForViewport_;
    const viewport = new Box(0, 200, 200, 0);
    const overflow = Overflow.RESIZE_HEIGHT;

    let pos = newCoord(150, 150);
    let size = newSize(25, 100);
    assertEquals(
        'Viewport height should be resized.', OverflowStatus.HEIGHT_ADJUSTED,
        f(pos, size, viewport, overflow));
    assertEquals('Height should be resized to 50.', 50, size.height);
    assertTrue(
        'Output box is within viewport',
        viewport.contains(
            new Box(pos.y, pos.x + size.width, pos.y + size.height, pos.x)));

    pos = newCoord(0, 0);
    size = newSize(50, 250);
    assertEquals(
        'Viewport height should be resized.', OverflowStatus.HEIGHT_ADJUSTED,
        f(pos, size, viewport, overflow));
    assertEquals('Height should be resized to 200.', 200, size.height);
    assertTrue(
        'Output box is within viewport',
        viewport.contains(
            new Box(pos.y, pos.x + size.width, pos.y + size.height, pos.x)));

    pos = newCoord(0, -50);
    size = newSize(50, 240);
    assertEquals(
        'Viewport height should be resized.', OverflowStatus.HEIGHT_ADJUSTED,
        f(pos, size, viewport, overflow));
    assertEquals('Height should be resized to 190.', 190, size.height);
    assertTrue(
        'Output box is within viewport',
        viewport.contains(
            new Box(pos.y, pos.x + size.width, pos.y + size.height, pos.x)));

    pos = newCoord(0, -50);
    size = newSize(50, 300);
    assertEquals(
        'Viewport height should be resized.', OverflowStatus.HEIGHT_ADJUSTED,
        f(pos, size, viewport, overflow));
    assertEquals('Height should be resized to 200.', 200, size.height);
    assertTrue(
        'Output box is within viewport',
        viewport.contains(
            new Box(pos.y, pos.x + size.width, pos.y + size.height, pos.x)));

    pos = newCoord(150, 150);
    size = newSize(50, 50);
    assertEquals(
        'No Viewport overflow.', OverflowStatus.NONE,
        f(pos, size, viewport, overflow));
    assertTrue(
        'Output box is within viewport',
        viewport.contains(
            new Box(pos.y, pos.x + size.width, pos.y + size.height, pos.x)));

    const offsetViewport = new Box(100, 200, 300, 0);
    pos = newCoord(0, 50);
    size = newSize(50, 240);
    assertEquals(
        'Viewport height should be resized.', OverflowStatus.HEIGHT_ADJUSTED,
        f(pos, size, offsetViewport, overflow));
    assertEquals('Height should be resized to 190.', 190, size.height);
    assertTrue(
        'Output box is within viewport',
        offsetViewport.contains(
            new Box(pos.y, pos.x + size.width, pos.y + size.height, pos.x)));
  },

  testAdjustForViewportResizeWidth() {
    /** @suppress {visibility} suppression added to enable type checking */
    const f = positioning.adjustForViewport_;
    const viewport = new Box(0, 200, 200, 0);
    const overflow = Overflow.RESIZE_WIDTH;

    let pos = newCoord(150, 150);
    let size = newSize(100, 25);
    assertEquals(
        'Viewport width should be resized.', OverflowStatus.WIDTH_ADJUSTED,
        f(pos, size, viewport, overflow));
    assertEquals('Width should be resized to 50.', 50, size.width);
    assertTrue(
        'Output box is within viewport',
        viewport.contains(
            new Box(pos.y, pos.x + size.width, pos.y + size.height, pos.x)));

    pos = newCoord(0, 0);
    size = newSize(250, 50);
    assertEquals(
        'Viewport width should be resized.', OverflowStatus.WIDTH_ADJUSTED,
        f(pos, size, viewport, overflow));
    assertEquals('Width should be resized to 200.', 200, size.width);
    assertTrue(
        'Output box is within viewport',
        viewport.contains(
            new Box(pos.y, pos.x + size.width, pos.y + size.height, pos.x)));

    pos = newCoord(-50, 0);
    size = newSize(240, 50);
    assertEquals(
        'Viewport width should be resized.', OverflowStatus.WIDTH_ADJUSTED,
        f(pos, size, viewport, overflow));
    assertEquals('Width should be resized to 190.', 190, size.width);
    assertTrue(
        'Output box is within viewport',
        viewport.contains(
            new Box(pos.y, pos.x + size.width, pos.y + size.height, pos.x)));

    pos = newCoord(-50, 0);
    size = newSize(300, 50);
    assertEquals(
        'Viewport width should be resized.', OverflowStatus.WIDTH_ADJUSTED,
        f(pos, size, viewport, overflow));
    assertEquals('Width should be resized to 200.', 200, size.width);
    assertTrue(
        'Output box is within viewport',
        viewport.contains(
            new Box(pos.y, pos.x + size.width, pos.y + size.height, pos.x)));

    pos = newCoord(150, 150);
    size = newSize(50, 50);
    assertEquals(
        'No Viewport overflow.', OverflowStatus.NONE,
        f(pos, size, viewport, overflow));
    assertTrue(
        'Output box is within viewport',
        viewport.contains(
            new Box(pos.y, pos.x + size.width, pos.y + size.height, pos.x)));

    const offsetViewport = new Box(0, 300, 200, 100);
    pos = newCoord(50, 0);
    size = newSize(240, 50);
    assertEquals(
        'Viewport width should be resized.', OverflowStatus.WIDTH_ADJUSTED,
        f(pos, size, offsetViewport, overflow));
    assertEquals('Width should be resized to 190.', 190, size.width);
    assertTrue(
        'Output box is within viewport',
        offsetViewport.contains(
            new Box(pos.y, pos.x + size.width, pos.y + size.height, pos.x)));
  },

  testPositionAtAnchorWithResizeHeight() {
    const anchor = document.getElementById('anchor9');
    const popup = document.getElementById('popup9');
    const box = document.getElementById('box9');
    const viewport = style.getBounds(box);

    const status = positioning.positionAtAnchor(
        anchor, corner.TOP_START, popup, corner.TOP_START,
        new Coordinate(0, -20), null, Overflow.RESIZE_HEIGHT, null,
        viewport.toBox());
    assertEquals(
        'Status should be HEIGHT_ADJUSTED.', OverflowStatus.HEIGHT_ADJUSTED,
        status);

    const TOLERANCE = 0.1;
    // Adjust the viewport to allow some tolerance for subpixel positioning,
    // this is required for this test to pass on IE10,11
    viewport.top -= TOLERANCE;
    viewport.left -= TOLERANCE;

    assertTrue(
        'Popup ' + style.getBounds(popup) + ' not is within viewport' +
            viewport,
        viewport.contains(style.getBounds(popup)));
  },

  testPositionAtCoordinateResizeHeight() {
    const f = positioning.positionAtCoordinate;
    const viewport = new Box(0, 50, 50, 0);
    const overflow = Overflow.RESIZE_HEIGHT | Overflow.ADJUST_Y;
    const popup = document.getElementById('popup1');
    const corner = Corner.BOTTOM_LEFT;

    const pos = newCoord(100, 100);

    assertEquals(
        'Viewport height should be resized.',
        OverflowStatus.HEIGHT_ADJUSTED | OverflowStatus.ADJUSTED_Y,
        f(pos, popup, corner, undefined, viewport, overflow));
    const bounds = style.getSize(popup);
    assertEquals(
        'Height should be resized to the size of the viewport.', 50,
        bounds.height);
  },

  testGetPositionAtCoordinateResizeHeight() {
    const f = positioning.getPositionAtCoordinate;
    const viewport = new Box(0, 50, 50, 0);
    const overflow = Overflow.RESIZE_HEIGHT | Overflow.ADJUST_Y;
    const popup = document.getElementById('popup1');
    const corner = Corner.BOTTOM_LEFT;

    const pos = newCoord(100, 100);
    const size = style.getSize(popup);

    const result = f(pos, size, corner, undefined, viewport, overflow);
    assertEquals(
        'Viewport height should be resized.',
        OverflowStatus.HEIGHT_ADJUSTED | OverflowStatus.ADJUSTED_Y,
        result.status);
    assertEquals(
        'Height should be resized to the size of the viewport.', 50,
        result.rect.height);
  },

  testGetEffectiveCornerLeftToRight() {
    const f = positioning.getEffectiveCorner;
    const el = document.getElementById('ltr');

    assertEquals(
        'TOP_LEFT should be unchanged for ltr.', corner.TOP_LEFT,
        f(el, corner.TOP_LEFT));
    assertEquals(
        'TOP_RIGHT should be unchanged for ltr.', corner.TOP_RIGHT,
        f(el, corner.TOP_RIGHT));
    assertEquals(
        'BOTTOM_LEFT should be unchanged for ltr.', corner.BOTTOM_LEFT,
        f(el, corner.BOTTOM_LEFT));
    assertEquals(
        'BOTTOM_RIGHT should be unchanged for ltr.', corner.BOTTOM_RIGHT,
        f(el, corner.BOTTOM_RIGHT));

    assertEquals(
        'TOP_START should be TOP_LEFT for ltr.', corner.TOP_LEFT,
        f(el, corner.TOP_START));
    assertEquals(
        'TOP_END should be TOP_RIGHT for ltr.', corner.TOP_RIGHT,
        f(el, corner.TOP_END));
    assertEquals(
        'BOTTOM_START should be BOTTOM_LEFT for ltr.', corner.BOTTOM_LEFT,
        f(el, corner.BOTTOM_START));
    assertEquals(
        'BOTTOM_END should be BOTTOM_RIGHT for ltr.', corner.BOTTOM_RIGHT,
        f(el, corner.BOTTOM_END));
  },

  testGetEffectiveCornerRightToLeft() {
    const f = positioning.getEffectiveCorner;
    const el = document.getElementById('rtl');

    assertEquals(
        'TOP_LEFT should be unchanged for rtl.', corner.TOP_LEFT,
        f(el, corner.TOP_LEFT));
    assertEquals(
        'TOP_RIGHT should be unchanged for rtl.', corner.TOP_RIGHT,
        f(el, corner.TOP_RIGHT));
    assertEquals(
        'BOTTOM_LEFT should be unchanged for rtl.', corner.BOTTOM_LEFT,
        f(el, corner.BOTTOM_LEFT));
    assertEquals(
        'BOTTOM_RIGHT should be unchanged for rtl.', corner.BOTTOM_RIGHT,
        f(el, corner.BOTTOM_RIGHT));

    assertEquals(
        'TOP_START should be TOP_RIGHT for rtl.', corner.TOP_RIGHT,
        f(el, corner.TOP_START));
    assertEquals(
        'TOP_END should be TOP_LEFT for rtl.', corner.TOP_LEFT,
        f(el, corner.TOP_END));
    assertEquals(
        'BOTTOM_START should be BOTTOM_RIGHT for rtl.', corner.BOTTOM_RIGHT,
        f(el, corner.BOTTOM_START));
    assertEquals(
        'BOTTOM_END should be BOTTOM_LEFT for rtl.', corner.BOTTOM_LEFT,
        f(el, corner.BOTTOM_END));
  },

  testFlipCornerHorizontal() {
    const f = positioning.flipCornerHorizontal;

    assertEquals(
        'TOP_LEFT should be flipped to TOP_RIGHT.', corner.TOP_RIGHT,
        f(corner.TOP_LEFT));
    assertEquals(
        'TOP_RIGHT should be flipped to TOP_LEFT.', corner.TOP_LEFT,
        f(corner.TOP_RIGHT));
    assertEquals(
        'BOTTOM_LEFT should be flipped to BOTTOM_RIGHT.', corner.BOTTOM_RIGHT,
        f(corner.BOTTOM_LEFT));
    assertEquals(
        'BOTTOM_RIGHT should be flipped to BOTTOM_LEFT.', corner.BOTTOM_LEFT,
        f(corner.BOTTOM_RIGHT));

    assertEquals(
        'TOP_START should be flipped to TOP_END.', corner.TOP_END,
        f(corner.TOP_START));
    assertEquals(
        'TOP_END should be flipped to TOP_START.', corner.TOP_START,
        f(corner.TOP_END));
    assertEquals(
        'BOTTOM_START should be flipped to BOTTOM_END.', corner.BOTTOM_END,
        f(corner.BOTTOM_START));
    assertEquals(
        'BOTTOM_END should be flipped to BOTTOM_START.', corner.BOTTOM_START,
        f(corner.BOTTOM_END));
  },

  testFlipCornerVertical() {
    const f = positioning.flipCornerVertical;

    assertEquals(
        'TOP_LEFT should be flipped to BOTTOM_LEFT.', corner.BOTTOM_LEFT,
        f(corner.TOP_LEFT));
    assertEquals(
        'TOP_RIGHT should be flipped to BOTTOM_RIGHT.', corner.BOTTOM_RIGHT,
        f(corner.TOP_RIGHT));
    assertEquals(
        'BOTTOM_LEFT should be flipped to TOP_LEFT.', corner.TOP_LEFT,
        f(corner.BOTTOM_LEFT));
    assertEquals(
        'BOTTOM_RIGHT should be flipped to TOP_RIGHT.', corner.TOP_RIGHT,
        f(corner.BOTTOM_RIGHT));

    assertEquals(
        'TOP_START should be flipped to BOTTOM_START.', corner.BOTTOM_START,
        f(corner.TOP_START));
    assertEquals(
        'TOP_END should be flipped to BOTTOM_END.', corner.BOTTOM_END,
        f(corner.TOP_END));
    assertEquals(
        'BOTTOM_START should be flipped to TOP_START.', corner.TOP_START,
        f(corner.BOTTOM_START));
    assertEquals(
        'BOTTOM_END should be flipped to TOP_END.', corner.TOP_END,
        f(corner.BOTTOM_END));
  },

  testFlipCorner() {
    const f = positioning.flipCorner;

    assertEquals(
        'TOP_LEFT should be flipped to BOTTOM_RIGHT.', corner.BOTTOM_RIGHT,
        f(corner.TOP_LEFT));
    assertEquals(
        'TOP_RIGHT should be flipped to BOTTOM_LEFT.', corner.BOTTOM_LEFT,
        f(corner.TOP_RIGHT));
    assertEquals(
        'BOTTOM_LEFT should be flipped to TOP_RIGHT.', corner.TOP_RIGHT,
        f(corner.BOTTOM_LEFT));
    assertEquals(
        'BOTTOM_RIGHT should be flipped to TOP_LEFT.', corner.TOP_LEFT,
        f(corner.BOTTOM_RIGHT));

    assertEquals(
        'TOP_START should be flipped to BOTTOM_END.', corner.BOTTOM_END,
        f(corner.TOP_START));
    assertEquals(
        'TOP_END should be flipped to BOTTOM_START.', corner.BOTTOM_START,
        f(corner.TOP_END));
    assertEquals(
        'BOTTOM_START should be flipped to TOP_END.', corner.TOP_END,
        f(corner.BOTTOM_START));
    assertEquals(
        'BOTTOM_END should be flipped to TOP_START.', corner.TOP_START,
        f(corner.BOTTOM_END));
  },

  testPositionAtAnchorFrameViewportStandard() {
    const iframe = document.getElementById('iframe-standard');
    const iframeDoc = dom.getFrameContentDocument(iframe);
    assertTrue(new DomHelper(iframeDoc).isCss1CompatMode());

    new DomHelper(iframeDoc).getDocumentScrollElement().scrollTop = 100;
    const anchor = iframeDoc.getElementById('anchor1');
    const popup = document.getElementById('popup6');

    const status = positioning.positionAtAnchor(
        anchor, corner.TOP_RIGHT, popup, corner.BOTTOM_RIGHT);
    const iframeRect = style.getBounds(iframe);
    const popupRect = style.getBounds(popup);
    assertEquals(
        'Status should not have any ADJUSTED and FAILED.', OverflowStatus.NONE,
        status);
    assertRoundedEquals(
        'Popup should be positioned just above the iframe, ' +
            'not above the anchor element inside the iframe',
        iframeRect.top, popupRect.top + popupRect.height);
  },

  testPositionAtAnchorFrameViewportQuirk() {
    const iframe = document.getElementById('iframe-quirk');
    const iframeDoc = dom.getFrameContentDocument(iframe);
    assertFalse(new DomHelper(iframeDoc).isCss1CompatMode());

    window.scrollTo(0, 100);
    new DomHelper(iframeDoc).getDocumentScrollElement().scrollTop = 100;
    const anchor = iframeDoc.getElementById('anchor1');
    const popup = document.getElementById('popup6');

    const status = positioning.positionAtAnchor(
        anchor, corner.TOP_RIGHT, popup, corner.BOTTOM_RIGHT);
    const iframeRect = style.getBounds(iframe);
    const popupRect = style.getBounds(popup);
    assertEquals(
        'Status should not have any ADJUSTED and FAILED.', OverflowStatus.NONE,
        status);
    assertRoundedEquals(
        'Popup should be positioned just above the iframe, ' +
            'not above the anchor element inside the iframe',
        iframeRect.top, popupRect.top + popupRect.height);
  },

  testPositionAtAnchorFrameViewportWithPopupInScroller() {
    const iframe = document.getElementById('iframe-standard');
    const iframeDoc = dom.getFrameContentDocument(iframe);

    new DomHelper(iframeDoc).getDocumentScrollElement().scrollTop = 100;
    const anchor = iframeDoc.getElementById('anchor1');
    const popup = document.getElementById('popup7');
    /**
     * @suppress {strictMissingProperties} suppression added to enable type
     * checking
     */
    popup.offsetParent.scrollTop = 50;

    const status = positioning.positionAtAnchor(
        anchor, corner.TOP_RIGHT, popup, corner.BOTTOM_RIGHT);
    const iframeRect = style.getBounds(iframe);
    const popupRect = style.getBounds(popup);
    assertEquals(
        'Status should not have any ADJUSTED and FAILED.', OverflowStatus.NONE,
        status);
    assertRoughlyEquals(
        'Popup should be positioned just above the iframe, ' +
            'not above the anchor element inside the iframe',
        iframeRect.top, popupRect.top + popupRect.height, ALLOWED_OFFSET);
  },

  testPositionAtAnchorNestedFrames() {
    const outerIframe = document.getElementById('nested-outer');
    const outerDoc = dom.getFrameContentDocument(outerIframe);
    const popup = outerDoc.getElementById('popup1');
    const innerIframe = outerDoc.getElementById('inner-frame');
    const innerDoc = dom.getFrameContentDocument(innerIframe);
    const anchor = innerDoc.getElementById('anchor1');

    let status = positioning.positionAtAnchor(
        anchor, corner.TOP_LEFT, popup, corner.BOTTOM_LEFT);
    assertEquals(
        'Status should not have any ADJUSTED and FAILED.', OverflowStatus.NONE,
        status);
    let innerIframeRect = style.getBounds(innerIframe);
    let popupRect = style.getBounds(popup);
    assertRoundedEquals(
        'Top of frame should align with bottom of the popup',
        innerIframeRect.top, popupRect.top + popupRect.height);

    // The anchor is scrolled up by 10px.
    // Popup position should be the same as above.
    dom.getWindow(innerDoc).scrollTo(0, 10);
    status = positioning.positionAtAnchor(
        anchor, corner.TOP_LEFT, popup, corner.BOTTOM_LEFT);
    assertEquals(
        'Status should not have any ADJUSTED and FAILED.', OverflowStatus.NONE,
        status);
    innerIframeRect = style.getBounds(innerIframe);
    popupRect = style.getBounds(popup);
    assertRoundedEquals(
        'Top of frame should align with bottom of the popup',
        innerIframeRect.top, popupRect.top + popupRect.height);
  },

  testPositionAtAnchorOffscreen() {
    const offset = 0;
    const anchor = dom.getElement('offscreen-anchor');
    const popup = dom.getElement('popup3');

    positioning.positionAtAnchor(
        anchor, corner.TOP_LEFT, popup, corner.TOP_LEFT, null, null,
        overflow.ADJUST_X | overflow.ADJUST_Y);
    assertObjectEquals(newCoord(offset, offset), style.getPageOffset(popup));

    positioning.positionAtAnchor(
        anchor, corner.TOP_LEFT, popup, corner.TOP_LEFT, null, null,
        overflow.ADJUST_X_EXCEPT_OFFSCREEN | overflow.ADJUST_Y);
    assertObjectEquals(newCoord(-1000, offset), style.getPageOffset(popup));

    positioning.positionAtAnchor(
        anchor, corner.TOP_LEFT, popup, corner.TOP_LEFT, null, null,
        overflow.ADJUST_X | overflow.ADJUST_Y_EXCEPT_OFFSCREEN);
    assertObjectEquals(newCoord(offset, -1000), style.getPageOffset(popup));

    positioning.positionAtAnchor(
        anchor, corner.TOP_LEFT, popup, corner.TOP_LEFT, null, null,
        overflow.ADJUST_X_EXCEPT_OFFSCREEN |
            overflow.ADJUST_Y_EXCEPT_OFFSCREEN);
    assertObjectEquals(newCoord(-1000, -1000), style.getPageOffset(popup));
  },

  /** @suppress {checkTypes} suppression added to enable type checking */
  testPositionAtAnchorWithOverflowScrollOffsetParent() {
    const testAreaOffset = style.getPageOffset(testArea);
    const scrollbarWidth = style.getScrollbarWidth();
    window.scrollTo(testAreaOffset.x, testAreaOffset.y);

    const overflowDiv = dom.createElement(TagName.DIV);
    overflowDiv.style.overflow = 'scroll';
    overflowDiv.style.position = 'relative';
    style.setSize(overflowDiv, 200 /* width */, 100 /* height */);

    const anchor = dom.createElement(TagName.DIV);
    anchor.style.position = 'absolute';
    style.setSize(anchor, 50 /* width */, 50 /* height */);
    style.setPosition(anchor, 300 /* left */, 300 /* top */);

    const popup = createPopupDiv(75 /* width */, 50 /* height */);

    dom.append(testArea, overflowDiv, anchor);
    dom.append(overflowDiv, popup);

    // Popup should always be positioned within the overflowDiv
    style.setPosition(overflowDiv, 0 /* left */, 0 /* top */);
    positioning.positionAtAnchor(
        anchor, corner.TOP_LEFT, popup, corner.TOP_RIGHT, null, null,
        overflow.ADJUST_X | overflow.ADJUST_Y);
    assertObjectRoughlyEquals(
        new Coordinate(
            testAreaOffset.x + 200 - 75 - scrollbarWidth,
            testAreaOffset.y + 100 - 50 - scrollbarWidth),
        style.getPageOffset(popup), 1);

    style.setPosition(overflowDiv, 400 /* left */, 0 /* top */);
    positioning.positionAtAnchor(
        anchor, corner.TOP_RIGHT, popup, corner.TOP_LEFT, null, null,
        overflow.ADJUST_X | overflow.ADJUST_Y);
    assertObjectRoughlyEquals(
        new Coordinate(
            testAreaOffset.x + 400,
            testAreaOffset.y + 100 - 50 - scrollbarWidth),
        style.getPageOffset(popup), 1);

    style.setPosition(overflowDiv, 0 /* left */, 400 /* top */);
    positioning.positionAtAnchor(
        anchor, corner.BOTTOM_LEFT, popup, corner.BOTTOM_RIGHT, null, null,
        overflow.ADJUST_X | overflow.ADJUST_Y);
    assertObjectRoughlyEquals(
        new Coordinate(
            testAreaOffset.x + 200 - 75 - scrollbarWidth,
            testAreaOffset.y + 400),
        style.getPageOffset(popup), 1);

    style.setPosition(overflowDiv, 400 /* left */, 400 /* top */);
    positioning.positionAtAnchor(
        anchor, corner.BOTTOM_RIGHT, popup, corner.BOTTOM_LEFT, null, null,
        overflow.ADJUST_X | overflow.ADJUST_Y);
    assertObjectRoughlyEquals(
        new Coordinate(testAreaOffset.x + 400, testAreaOffset.y + 400),
        style.getPageOffset(popup), 1);

    // No overflow.
    style.setPosition(overflowDiv, 300 - 50 /* left */, 300 /* top */);
    positioning.positionAtAnchor(
        anchor, corner.TOP_LEFT, popup, corner.TOP_RIGHT, null, null,
        overflow.ADJUST_X | overflow.ADJUST_Y);
    assertObjectRoughlyEquals(
        new Coordinate(testAreaOffset.x + 300 - 50, testAreaOffset.y + 300),
        style.getPageOffset(popup), 1);
  },

  /** @suppress {checkTypes} suppression added to enable type checking */
  testPositionAtAnchorWithOverflowHiddenParent() {
    const testAreaOffset = style.getPageOffset(testArea);
    window.scrollTo(testAreaOffset.x, testAreaOffset.y);

    const overflowDiv = dom.createElement(TagName.DIV);
    overflowDiv.style.overflow = 'hidden';
    overflowDiv.style.position = 'relative';
    style.setSize(overflowDiv, 200 /* width */, 100 /* height */);

    const anchor = dom.createElement(TagName.DIV);
    anchor.style.position = 'absolute';
    style.setSize(anchor, 50 /* width */, 50 /* height */);
    style.setPosition(anchor, 300 /* left */, 300 /* top */);

    const popup = createPopupDiv(75 /* width */, 50 /* height */);

    dom.append(testArea, overflowDiv, anchor);
    dom.append(overflowDiv, popup);

    // Popup should always be positioned within the overflowDiv
    style.setPosition(overflowDiv, 0 /* left */, 0 /* top */);
    positioning.positionAtAnchor(
        anchor, corner.TOP_LEFT, popup, corner.TOP_RIGHT, null, null,
        overflow.ADJUST_X | overflow.ADJUST_Y);
    assertObjectRoughlyEquals(
        new Coordinate(
            testAreaOffset.x + 200 - 75, testAreaOffset.y + 100 - 50),
        style.getPageOffset(popup), 1);

    style.setPosition(overflowDiv, 400 /* left */, 0 /* top */);
    positioning.positionAtAnchor(
        anchor, corner.TOP_RIGHT, popup, corner.TOP_LEFT, null, null,
        overflow.ADJUST_X | overflow.ADJUST_Y);
    assertObjectRoughlyEquals(
        new Coordinate(testAreaOffset.x + 400, testAreaOffset.y + 100 - 50),
        style.getPageOffset(popup), 1);

    style.setPosition(overflowDiv, 0 /* left */, 400 /* top */);
    positioning.positionAtAnchor(
        anchor, corner.BOTTOM_LEFT, popup, corner.BOTTOM_RIGHT, null, null,
        overflow.ADJUST_X | overflow.ADJUST_Y);
    assertObjectRoughlyEquals(
        new Coordinate(testAreaOffset.x + 200 - 75, testAreaOffset.y + 400),
        style.getPageOffset(popup), 1);

    style.setPosition(overflowDiv, 400 /* left */, 400 /* top */);
    positioning.positionAtAnchor(
        anchor, corner.BOTTOM_RIGHT, popup, corner.BOTTOM_LEFT, null, null,
        overflow.ADJUST_X | overflow.ADJUST_Y);
    assertObjectRoughlyEquals(
        new Coordinate(testAreaOffset.x + 400, testAreaOffset.y + 400),
        style.getPageOffset(popup), 1);

    // No overflow.
    style.setPosition(overflowDiv, 300 - 50 /* left */, 300 /* top */);
    positioning.positionAtAnchor(
        anchor, corner.TOP_LEFT, popup, corner.TOP_RIGHT, null, null,
        overflow.ADJUST_X | overflow.ADJUST_Y);
    assertObjectRoughlyEquals(
        new Coordinate(testAreaOffset.x + 300 - 50, testAreaOffset.y + 300),
        style.getPageOffset(popup), 1);
  },
});
