chromium/third_party/blink/web_tests/editing/caret/bidi_hit_test_caret_consistency.html

<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<style>
div.test {
  font-size: 20px;
  width: 120px;
}
</style>
<body>
<script>
// This file tests the consistency between hit test and caret displaying that,
// caret must be displayed at the exact location where mouse is clicked.

const kErrorThreshold = 2;

function computeExpectedCaretRect(container, characterIndex, side) {
  const range = container.ownerDocument.createRange();
  range.setStart(container.firstChild, characterIndex);
  range.setEnd(container.firstChild, characterIndex + 1);
  const rect = range.getBoundingClientRect();

  if (side == 'left')
    return {x: rect.left, top: rect.top, height: rect.height};
  return {x: rect.right, top: rect.top, height: rect.height};
}

function clickAt(container, characterIndex, side) {
  const caretRect = computeExpectedCaretRect(container, characterIndex, side);

  const xDelta = side === 'left' ? kErrorThreshold : -kErrorThreshold;
  const x = caretRect.x + xDelta;
  const y = caretRect.top + caretRect.height / 2;

  return new Promise((resolve, reject) => {
    assert_own_property(window, 'chrome');
    assert_own_property(window.chrome, 'gpuBenchmarking');

    chrome.gpuBenchmarking.pointerActionSequence([{
      source: 'mouse',
      actions: [
        {name: 'pointerDown', x: x, y: y},
        {name: 'pointerUp'}
      ]}], resolve);
  });
}

function assertCaretLocation(container, characterIndex, side) {
  const expectedCaretRect = computeExpectedCaretRect(container, characterIndex, side);
  const actualCaretRect = internals.absoluteCaretBounds();

  assert_approx_equals(actualCaretRect.x, expectedCaretRect.x, kErrorThreshold);
  assert_approx_equals(actualCaretRect.top, expectedCaretRect.top, kErrorThreshold);
}

tests = [
  // Rendered as "foo CBA" in LTR block. Click the right edge of "A".
  {html: 'foo  \u05D0\u05D1\u05D2', dir: 'ltr', index: 5, side: 'right', name: 'Bidi after collapsed space LTR'},
  // Rendered as "foo CBA" in RTL block. Click the left edge of "f".
  {html: '\u05D0\u05D1\u05D2  foo', dir: 'rtl', index: 5, side: 'left', name: 'Bidi after collapsed space RTL'},
  // Rendered as two lines in LTR block:
  // "foo CBA"
  // "LKJIHGFED"
  // Click the left edge of "L".
  {html: 'foo  \u05D0\u05D1\u05D2 \u05D3\u05D4\u05D5\u05D6\u05D7\u05D8\u05D9\u05DA\u05DB',
   dir: 'ltr', index: 16, side: 'left', name: 'End of RTL text in LTR block after line wrap'},
  // Same sample as above. Click the left edge of "C".
  {html: 'foo  \u05D0\u05D1\u05D2 \u05D3\u05D4\u05D5\u05D6\u05D7\u05D8\u05D9\u05DA\u05DB',
   dir: 'ltr', index: 7, side: 'left', name: 'Logical line end in LTR block before line wrap'},
  // Rendered as two lines in RTL block:
  // "foo CBA"
  // "abcdefghijklmn"
  // Click the right edge of "n".
  {html: '\u05D0\u05D1\u05D2  foo abcdefghijklmn',
   dir: 'rtl', index: 22, side: 'right', name: 'End of LTR text in RTL block after line wrap'},
  // Same sample as above. Click the right edge of "foo".
  {html: '\u05D0\u05D1\u05D2  foo abcdefghijklmn',
   dir: 'rtl', index: 7, side: 'right', name: 'Logical line end in RTL block before line wrap'},
];

function runTest(testCase) {
  const html = testCase.html;
  const dir = testCase.dir;
  const index = testCase.index;
  const side = testCase.side;
  const name = testCase.name;
  promise_test(async () => {
    const container = document.createElement('div');
    container.classList.add('test');
    container.contentEditable = 'true';
    container.dir = dir;
    container.innerHTML = html;
    document.body.appendChild(container);

    await clickAt(container, index, side);
    assertCaretLocation(container, index, side);
  }, name);
}

tests.forEach(runTest);
</script>
</body>