<!DOCTYPE html>
<meta charset="utf-8">
<title>Caret navigation around line break</title>
<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
<meta name="assert" content="This test checks that caret navigation works well around various kinds of line breaks." />
<meta name="timeout" content="long">
<style>
.test {
  font-size: 16px;
  line-height: 20px;
  padding: 4px;
  width: 5.5ch;
  padding: 5px;
  font-family: monospace;
  word-wrap: break-word;
}
</style>

<div class="test" contenteditable data-title="no separator"
    >line1line2</div>
<div class="test" contenteditable data-title="<br> separator"
    >line1<br>line2</div>
<div class="test" contenteditable data-title="<wbr> separator"
    >line1<wbr>line2</div>
<div class="test" contenteditable data-title="<span> separator"
    >line1<span></span>line2</div>
<div class="test" contenteditable data-title="two <span> separators"
    >line1<span></span><span></span>line2</div>

<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script>
const KEY_CODE_MAP = {
  'ArrowLeft':  '\uE012',
  'ArrowUp':    '\uE013',
  'ArrowRight': '\uE014',
  'ArrowDown':  '\uE015',
};

function click(target, x, y) {
  return new test_driver.Actions()
    .pointerMove(x, y, {origin: target})
    .pointerDown()
    .pointerUp()
    .send();
}

const s = getSelection();
for (const test of document.querySelectorAll(".test")) {
  const padding = 4;
  const halfLineWidth = Math.floor((test.offsetWidth - padding) / 2);
  const halfLineHeight = Math.floor(20 / 2);
  const hasSeparator = test.firstChild !== test.lastChild;
  const line1 = {
    node: test.firstChild,
    start: 0,
    end: "line1".length,
  };
  const line2 = {
    node: test.lastChild,
    start: hasSeparator ? 0 : "line1".length,
    end: hasSeparator ? "line2".length : "line1line2".length,
  };

  promise_test(async t => {
    // Click at the start of line 1
    await click(test, -halfLineWidth, -halfLineHeight);
    assert_equals(s.anchorNode, line1.node, "Caret is in line 1");
    assert_equals(s.anchorOffset, line1.start, "Caret is at the start of line 1");

    // Move down, expect start of line 2
    await test_driver.send_keys(test, KEY_CODE_MAP.ArrowDown);
    assert_equals(s.anchorNode, line2.node, "Caret moved to line 2");
    assert_equals(s.anchorOffset, line2.start, "Caret moved to the start of line 2");

    // Click at the end of line 1
    await click(test, +halfLineWidth, -halfLineHeight);
    range = getSelection().getRangeAt(0);
    assert_equals(s.anchorNode, line1.node, "Caret is in line 1");
    assert_equals(s.anchorOffset, line1.end, "Caret is at the end of line 1");

    // Move down, expect end of line 2
    await test_driver.send_keys(test, KEY_CODE_MAP.ArrowDown);
    assert_equals(s.anchorNode, line2.node, "Caret moved to line 2");
    assert_equals(s.anchorOffset, line2.end, "Caret moved to the end of line 2");
  }, test.dataset.title + " - move down");

  promise_test(async t => {
    // Click at the start of line 2
    await click(test, -halfLineWidth, +halfLineHeight);
    assert_equals(s.anchorNode, line2.node, "Caret is in line 2");
    assert_equals(s.anchorOffset, line2.start, "Caret is at the start of line 2");

    // Move up, expect start of line 1
    await test_driver.send_keys(test, KEY_CODE_MAP.ArrowUp);
    assert_equals(s.anchorNode, line1.node, "Caret moved to line 1");
    assert_equals(s.anchorOffset, line1.start, "Caret moved to the start of line 1");

    // Click at the end of line 2
    await click(test, +halfLineWidth, +halfLineHeight);
    assert_equals(s.anchorNode, line2.node, "Caret is in line 2");
    assert_equals(s.anchorOffset, line2.end, "Caret is at the end of line 2");

    // Move up, expect end of line 1
    await test_driver.send_keys(test, KEY_CODE_MAP.ArrowUp);
    assert_equals(s.anchorNode, line1.node, "Caret moved to line 1");
    assert_equals(s.anchorOffset, line1.end, "Caret moved to the end of line 1");
  }, test.dataset.title + " - move up");

  promise_test(async t => {
    // Click at the end of line 1
    await click(test, +halfLineWidth, -halfLineHeight);
    assert_equals(s.anchorNode, line1.node, "Caret is in line 1");
    assert_equals(s.anchorOffset, line1.end, "Caret is at the end of line 1");

    // Move right, expect start or start+1 of line 2
    await test_driver.send_keys(test, KEY_CODE_MAP.ArrowRight);
    assert_equals(s.anchorNode, line2.node, "Caret moved to line 2");
    assert_in_array(s.anchorOffset, [line2.start, line2.start + 1], "Caret moved to the start or start+1 of line 2");
  }, test.dataset.title + " - move right");

  promise_test(async t => {
    // Click at the start of line 2
    await click(test, -halfLineWidth, +halfLineHeight);
    assert_equals(s.anchorNode, line2.node, "Caret is in line 2");
    assert_equals(s.anchorOffset, line2.start, "Caret is at the start of line 2");

    // Move left, expect end or end-1 of line 1
    await test_driver.send_keys(test, KEY_CODE_MAP.ArrowLeft);
    assert_equals(s.anchorNode, line1.node, "Caret moved to line 1");
    assert_in_array(s.anchorOffset, [line1.end, line1.end - 1], "Caret moved to the end or end-1 of line 1");
  }, test.dataset.title + " - move left");
}
</script>
