chromium/third_party/blink/web_tests/external/wpt/editing/other/delete-without-unwrapping-first-line-of-child-block.html

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="timeout" content="long">
<meta name="variant" content="?method=BackspaceKey&lineBreak=br">
<meta name="variant" content="?method=DeleteKey&lineBreak=br">
<meta name="variant" content="?method=deleteCommand&lineBreak=br">
<meta name="variant" content="?method=forwardDeleteCommand&lineBreak=br">
<meta name="variant" content="?method=BackspaceKey&lineBreak=preformat">
<meta name="variant" content="?method=DeleteKey&lineBreak=preformat">
<meta name="variant" content="?method=deleteCommand&lineBreak=preformat">
<meta name="variant" content="?method=forwardDeleteCommand&lineBreak=preformat">
<title>Tests for deleting preceding lines of right child block if range ends at start of the right child</title>
<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 src="../include/editor-test-utils.js"></script>
<script>
"use strict";

/**
 * Browsers delete only preceding lines (and selected content in the child
 * block) when the deleting range starts from a line and ends in a child block
 * without unwrapping the (new) first line of the child block at end.  Note that
 * this is a special handling for the above case, i.e., if the range starts from
 * a middle of a preceding line of the child block, the first line of the child
 * block should be unwrapped and merged into the preceding line.  This is also
 * applied when the range is directly replaced with new content like typing a
 * character.  Finally, selection should be collapsed at start of the child
 * block and new content should be inserted at start of the child block.
 *
 * This file also tests getTargetRanges() of `beforeinput` of at deletion and
 * replacing the selection directly.  In the former case, if the range ends at
 * start of the child block, browsers do not touch the child block.  Therefore,
 * the target ranges should the a range deleting the preceding lines, i.e.,
 * should be end at the child block.  When the range is replaced directly, the
 * content will be inserted at start of the child block, and also when the range
 * selects some content in the child block, browsers touch the child block.
 * Therefore, the target range should end at the next insertion point.
 */

const searchParams = new URLSearchParams(document.location.search);
const testUserInput = searchParams.get("method") == "BackspaceKey" || searchParams.get("method") == "DeleteKey";
const testBackward = searchParams.get("method") == "BackspaceKey" || searchParams.get("method") == "deleteCommand";
const deleteMethod =
  testUserInput
    ? testBackward ? "Backspace" : "Delete"
    : `document.execCommand("${testBackward ? "delete" : "forwarddelete"}")`;
const insertTextMethod = testUserInput ? "Typing \"X\"" : "document.execCommand(\"insertText\", false, \"X\")";
const lineBreak = searchParams.get("lineBreak") == "br" ? "<br>" : "\n";
const lineBreakIsBR = lineBreak == "<br>";

function run(editorUtils) {
  if (testUserInput) {
    return testBackward ? editorUtils.sendBackspaceKey() : editorUtils.sendDeleteKey();
  }
  editorUtils.document.execCommand(testBackward ? "delete" : "forwardDelete");
}

function typeCharacter(editorUtils, ch) {
  if (testUserInput) {
    return editorUtils.sendKey(ch);
  }
  document.execCommand("insertText", false, ch);
}

async function runDeleteTest(
  runningTest,
  testUtils,
  initialInnerHTML,
  expectedAfterDeletion,
  whatShouldHappenAfterDeletion,
  expectedAfterDeletionAndInsertion,
  whatShouldHappenAfterDeletionAndInsertion,
  expectedTargetRangesAtDeletion,
  whatGetTargetRangesShouldReturn
) {
  let targetRanges = [];
  if (testUserInput) {
    testUtils.editingHost.addEventListener(
      "beforeinput",
      event => targetRanges = event.getTargetRanges(),
      {once: true}
    );
  }
  await run(testUtils);
  (Array.isArray(expectedAfterDeletion) ? assert_in_array : assert_equals)(
    testUtils.editingHost.innerHTML,
    expectedAfterDeletion,
    `${runningTest.name} ${whatShouldHappenAfterDeletion}`
  );
  if (testUserInput) {
    test(() => {
      const arrayOfStringifiedExpectedTargetRanges = (() => {
        let arrayOfTargetRanges = [];
        for (const expectedTargetRanges of expectedTargetRangesAtDeletion) {
          arrayOfTargetRanges.push(
            EditorTestUtils.getRangeArrayDescription(expectedTargetRanges)
          );
        }
        return arrayOfTargetRanges;
      })();
      assert_in_array(
        EditorTestUtils.getRangeArrayDescription(targetRanges),
        arrayOfStringifiedExpectedTargetRanges
      );
    }, `getTargetRanges() for ${runningTest.name} ${whatGetTargetRangesShouldReturn}`);
  }
  await typeCharacter(testUtils, "X");
  (Array.isArray(expectedAfterDeletionAndInsertion) ? assert_in_array : assert_equals)(
    testUtils.editingHost.innerHTML,
    expectedAfterDeletionAndInsertion,
    `${insertTextMethod} after ${runningTest.name} ${whatShouldHappenAfterDeletionAndInsertion}`
  );
}

async function runReplacingTest(
  runningTest,
  testUtils,
  initialInnerHTML,
  expectedAfterReplacing,
  whatShouldHappenAfterReplacing,
  expectedTargetRangesAtReplace,
  whatGetTargetRangesShouldReturn
) {
  let targetRanges = [];
  if (testUserInput) {
    testUtils.editingHost.addEventListener(
      "beforeinput",
      event => targetRanges = event.getTargetRanges(),
      {once: true}
    );
  }
  await typeCharacter(testUtils, "X");
  (Array.isArray(expectedAfterReplacing) ? assert_in_array : assert_equals)(
    testUtils.editingHost.innerHTML,
    expectedAfterReplacing,
    `${runningTest.name} ${whatShouldHappenAfterReplacing}`
  );
  if (testUserInput) {
    test(() => {
      const arrayOfStringifiedExpectedTargetRanges = (() => {
        let arrayOfTargetRanges = [];
        for (const expectedTargetRanges of expectedTargetRangesAtReplace) {
          arrayOfTargetRanges.push(
            EditorTestUtils.getRangeArrayDescription(expectedTargetRanges)
          );
        }
        return arrayOfTargetRanges;
      })();
      assert_in_array(
        EditorTestUtils.getRangeArrayDescription(targetRanges),
        arrayOfStringifiedExpectedTargetRanges
      );
    }, `getTargetRanges() for ${runningTest.name} ${whatGetTargetRangesShouldReturn}`);
  }
}

addEventListener("load", () => {
  const editingHost = document.querySelector("div[contenteditable]");
  const selStart = lineBreakIsBR ? "{" : "[";
  const selCollapsed = lineBreakIsBR ? "{}" : "[]";
  editingHost.style.whiteSpace = lineBreakIsBR ? "normal" : "pre";
  const testUtils = new EditorTestUtils(editingHost);
  (() => {
    const initialInnerHTML =
      `abc${lineBreak}${selStart}${lineBreak}<div id="child">]def<br>ghi</div>`;
    promise_test(async t => {
      testUtils.setupEditingHost(initialInnerHTML);
      await runDeleteTest(
        t, testUtils, initialInnerHTML,
        [
          `abc${lineBreak}<div id="child">def<br>ghi</div>`,
          `abc<div id="child">def<br>ghi</div>`,
        ],
        "should delete only the preceding empty line of the child <div>",
        [
          `abc${lineBreak}<div id="child">Xdef<br>ghi</div>`,
          `abc<div id="child">Xdef<br>ghi</div>`,
        ],
        "should insert text into the child <div>",
        lineBreakIsBR
          ? [
              // abc<br>{<br>}<div>
              [{ startContainer: editingHost, startOffset: 2, endContainer: editingHost, endOffset: 3 }],
              // abc{<br><br>}<div>
              [{ startContainer: editingHost, startOffset: 1, endContainer: editingHost, endOffset: 3 }],
              // abc[<br><br>}<div>
              [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: editingHost, endOffset: 3 }],
            ]
          : [
              // abc\n[\n}<div>
              [{ startContainer: editingHost.firstChild, startOffset: "abc\n".length, endContainer: editingHost, endOffset: 1 }],
              // abc[\n\n}<div>
              [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: editingHost, endOffset: 1 }],
              // abc\n[\n]<div>
              [{ startContainer: editingHost.firstChild, startOffset: "abc\n".length, endContainer: editingHost.firstChild, endOffset: "abc\n\n".length }],
              // abc[\n\n]<div>
              [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: editingHost.firstChild, endOffset: "abc\n\n".length }],
            ],
        "should return a range before the child <div>"
      );
    }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`);

    promise_test(async t => {
      testUtils.setupEditingHost(initialInnerHTML);
      const firstTextInChildDiv = editingHost.querySelector("div").firstChild;
      await runReplacingTest(
        t, testUtils, initialInnerHTML,
        [
          `abc${lineBreak}<div id="child">Xdef<br>ghi</div>`,
          `abc<div id="child">Xdef<br>ghi</div>`,
        ],
        "should not unwrap the first line of the child <div>",
        lineBreakIsBR
          ? [
              // abc<br>{<br><div>]def
              [{ startContainer: editingHost, startOffset: 2, endContainer: firstTextInChildDiv, endOffset: 0 }],
              // abc{<br><br><div>]def
              [{ startContainer: editingHost, startOffset: 1, endContainer: firstTextInChildDiv, endOffset: 0 }],
              // abc[<br><br><div>]def
              [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: firstTextInChildDiv, endOffset: 0 }],
            ]
          : [
              // abc\n[\n<div>]def
              [{ startContainer: editingHost.firstChild, startOffset: "abc\n".length, endContainer: firstTextInChildDiv, endOffset: 0 }],
              // abc[\n\n<div>]def
              [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: firstTextInChildDiv, endOffset: 0 }],
            ],
        "should return a range ending in the child <div>"
      );
    }, `${insertTextMethod} at ${initialInnerHTML.replaceAll("\n", "\\n")}`);
  })();

  (() => {
    const initialInnerHTML =
      `${lineBreak}[abc${lineBreak}<div id="child">]def<br>ghi</div>`;
    promise_test(async t => {
      testUtils.setupEditingHost(initialInnerHTML);
      await runDeleteTest(
        t, testUtils, initialInnerHTML,
        `${lineBreak}<div id="child">def<br>ghi</div>`,
        "should delete only the preceding empty line of the child <div>",
        `${lineBreak}<div id="child">Xdef<br>ghi</div>`,
        "should insert text into the child <div>",
        lineBreakIsBR
          ? [
              // <br>[abc<br>}<div>
              [{ startContainer: editingHost.firstChild.nextSibling, startOffset: 0, endContainer: editingHost, endOffset: 3 }],
            ]
          : [
              // \n[abc\n}<div>
              [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: editingHost, endOffset: 1 }],
              // \n[abc\n]<div>
              [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: editingHost.firstChild, endOffset: "\nabc\n".length }],
            ],
        "should return a range before the child <div>"
      );
    }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`);

    promise_test(async t => {
      testUtils.setupEditingHost(initialInnerHTML);
      const firstTextInChildDiv = editingHost.querySelector("div").firstChild;
      await runReplacingTest(
        t, testUtils, initialInnerHTML,
        `${lineBreak}<div id="child">Xdef<br>ghi</div>`,
        "should not unwrap the first line of the child <div>",
        lineBreakIsBR
          ? [
              // <br>[abc<br><div>]def
              [{ startContainer: editingHost.firstChild.nextSibling, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 0 }],
            ]
          : [
              // \n[abc\n<div>]def
              [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: firstTextInChildDiv, endOffset: 0 }],
            ],
        "should return a range ending in the child <div>"
      );
    }, `${insertTextMethod} at ${initialInnerHTML.replaceAll("\n", "\\n")}`);
  })();

  (() => {
    const initialInnerHTML =
      `${lineBreak}${selStart}${lineBreak}<div id="child">]def<br>ghi</div>`;
    promise_test(async t => {
      testUtils.setupEditingHost(initialInnerHTML);
      await runDeleteTest(
        t, testUtils, initialInnerHTML,
        `${lineBreak}<div id="child">def<br>ghi</div>`,
        "should delete only the preceding empty line of the child <div>",
        `${lineBreak}<div id="child">Xdef<br>ghi</div>`,
        "should insert text into the child <div>",
        lineBreakIsBR
          ? [
              // <br>{<br>}<div>
              [{ startContainer: editingHost, startOffset: 1, endContainer: editingHost, endOffset: 2 }],
            ]
          : [
              // \n[\n}<div>
              [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: editingHost, endOffset: 1 }],
              // \n[\n]<div>
              [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: editingHost.firstChild, endOffset: "\n\n".length }],
            ],
        "should return a range before the child <div>"
      );
    }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`);

    promise_test(async t => {
      testUtils.setupEditingHost(initialInnerHTML);
      const firstTextInChildDiv = editingHost.querySelector("div").firstChild;
      await runReplacingTest(
        t, testUtils, initialInnerHTML,
        `${lineBreak}<div id="child">Xdef<br>ghi</div>`,
        "should not unwrap the first line of the child <div>",
        lineBreakIsBR
          ? [
              // <br>{<br><div>]def
              [{ startContainer: editingHost, startOffset: 1, endContainer: firstTextInChildDiv, endOffset: 0 }],
            ]
          : [
              // \n[\n<div>]def
              [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: firstTextInChildDiv, endOffset: 0 }],
            ],
        "should return a range ending in the child <div>"
      );
    }, `${insertTextMethod} at ${initialInnerHTML.replaceAll("\n", "\\n")}`);
  })();

  (() => {
    const initialInnerHTML =
      `${selStart}${lineBreak}${lineBreak}<div id="child">]def<br>ghi</div>`;
    promise_test(async t => {
      testUtils.setupEditingHost(initialInnerHTML);
      await runDeleteTest(
        t, testUtils, initialInnerHTML,
        `<div id="child">def<br>ghi</div>`,
        "should delete only the preceding empty line of the child <div>",
        `<div id="child">Xdef<br>ghi</div>`,
        "should insert text into the child <div>",
        lineBreakIsBR
          ? [
              // {<br><br>}<div>
              [{ startContainer: editingHost, startOffset: 0, endContainer: editingHost, endOffset: 2 }],
            ]
          : [
              // [\n\n}<div>
              [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: editingHost, endOffset: 1 }],
              // [\n\n}<div>
              [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: editingHost.firstChild, endOffset: "\n\n".length }],
            ],
        "should return a range before the child <div>"
      );
    }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`);

    promise_test(async t => {
      testUtils.setupEditingHost(initialInnerHTML);
      const firstTextInChildDiv = editingHost.querySelector("div").firstChild;
      await runReplacingTest(
        t, testUtils, initialInnerHTML,
        `<div id="child">Xdef<br>ghi</div>`,
        "should not unwrap the first line of the child <div>",
        lineBreakIsBR
          ? [
              // {<br><br><div>]def
              [{ startContainer: editingHost, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 0 }],
            ]
          : [
              // [\n\n<div>]def
              [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 0 }],
            ],
        "should return a range ending in the child <div>"
      );
    }, `${insertTextMethod} at ${initialInnerHTML.replaceAll("\n", "\\n")}`);
  })();

  (() => {
    const initialInnerHTML =
      `[abc${lineBreak}${lineBreak}<div id="child">]def<br>ghi</div>`;
    promise_test(async t => {
      testUtils.setupEditingHost(initialInnerHTML);
      await runDeleteTest(
        t, testUtils, initialInnerHTML,
        `<div id="child">def<br>ghi</div>`,
        "should delete only the preceding empty line of the child <div>",
        `<div id="child">Xdef<br>ghi</div>`,
        "should insert text into the child <div>",
        lineBreakIsBR
          ? [
              // [abc<br><br>}<div>
              [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: editingHost, endOffset: 3 }],
            ]
          : [
              // {abc\n\n}<div>
              [{ startContainer: editingHost, startOffset: 0, endContainer: editingHost, endOffset: 1 }],
              // [abc\n\n}<div>
              [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: editingHost, endOffset: 1 }],
              // [abc\n\n}<div>
              [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: editingHost.firstChild, endOffset: "abc\n\n".length }],
            ],
        "should return a range before the child <div>"
      );
    }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`);

    promise_test(async t => {
      testUtils.setupEditingHost(initialInnerHTML);
      const firstTextInChildDiv = editingHost.querySelector("div").firstChild;
      await runReplacingTest(
        t, testUtils, initialInnerHTML,
        `<div id="child">Xdef<br>ghi</div>`,
        "should not unwrap the first line of the child <div>",
        lineBreakIsBR
          ? [
              // [abc<br><div>]def
              [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 0 }],
            ]
          : [
              // [abc\n<div>]def
              [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 0 }],
            ],
        "should return a range ending in the child <div>"
      );
    }, `${insertTextMethod} at ${initialInnerHTML.replaceAll("\n", "\\n")}`);
  })();

  (() => {
    const initialInnerHTML =
      `abc${lineBreak}${selStart}${lineBreak}<div id="child">d]ef<br>ghi</div>`;
    promise_test(async t => {
      testUtils.setupEditingHost(initialInnerHTML);
      const firstTextInChildDiv = editingHost.querySelector("div").firstChild;
      await runDeleteTest(
        t, testUtils, initialInnerHTML,
        [
          `abc${lineBreak}<div id="child">ef<br>ghi</div>`,
          `abc<div id="child">ef<br>ghi</div>`,
        ],
        "should delete only the preceding empty line of the child <div> and selected text in the <div>",
        [
          `abc${lineBreak}<div id="child">Xef<br>ghi</div>`,
          `abc<div id="child">Xef<br>ghi</div>`,
        ],
        "should insert text into the child <div>",
        lineBreakIsBR
          ? [
              // abc<br>{<br><div>d]ef
              [{ startContainer: editingHost, startOffset: 2, endContainer: firstTextInChildDiv, endOffset: 1 }],
              // abc{<br><br><div>d]ef
              [{ startContainer: editingHost, startOffset: 1, endContainer: firstTextInChildDiv, endOffset: 1 }],
              // abc[<br><br><div>d]ef
              [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: firstTextInChildDiv, endOffset: 1 }],
            ]
          : [
              // abc\n[\n}<div>d]ef
              [{ startContainer: editingHost.firstChild, startOffset: "abc\n".length, endContainer: firstTextInChildDiv, endOffset: 1 }],
              // abc[\n\n}<div>d]ef
              [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: firstTextInChildDiv, endOffset: 1 }],
            ],
        "should return a range ends at start of the child <div>"
      );
    }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`);

    promise_test(async t => {
      testUtils.setupEditingHost(initialInnerHTML);
      const firstTextInChildDiv = editingHost.querySelector("div").firstChild;
      await runReplacingTest(
        t, testUtils, initialInnerHTML,
        [
          `abc${lineBreak}<div id="child">Xef<br>ghi</div>`,
          `abc<div id="child">Xef<br>ghi</div>`,
        ],
        "should not unwrap the first line of the child <div>",
        lineBreakIsBR
          ? [
              // abc<br>{<br><div>d]ef
              [{ startContainer: editingHost, startOffset: 2, endContainer: firstTextInChildDiv, endOffset: 1 }],
              // abc{<br><br><div>d]ef
              [{ startContainer: editingHost, startOffset: 1, endContainer: firstTextInChildDiv, endOffset: 1 }],
              // abc[<br><br><div>d]ef
              [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: firstTextInChildDiv, endOffset: 1 }],
            ]
          : [
              // abc\n[\n<div>d]ef
              [{ startContainer: editingHost.firstChild, startOffset: "abc\n".length, endContainer: firstTextInChildDiv, endOffset: 1 }],
              // abc[\n\n<div>d]ef
              [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: firstTextInChildDiv, endOffset: 1 }],
            ],
        "should return a range ends at start of the child <div>"
      );
    }, `${insertTextMethod} at ${initialInnerHTML.replaceAll("\n", "\\n")}`);
  })();

  (() => {
    const initialInnerHTML =
      `${lineBreak}[abc${lineBreak}<div id="child">d]ef<br>ghi</div>`;
    promise_test(async t => {
      testUtils.setupEditingHost(initialInnerHTML);
      const firstTextInChildDiv = editingHost.querySelector("div").firstChild;
      await runDeleteTest(
        t, testUtils, initialInnerHTML,
        `${lineBreak}<div id="child">ef<br>ghi</div>`,
        "should delete only the preceding empty line of the child <div> and the selected content in the <div>",
        `${lineBreak}<div id="child">Xef<br>ghi</div>`,
        "should insert text into the child <div>",
        lineBreakIsBR
          ? [
              // <br>[abc<br><div>d]ef
              [{ startContainer: editingHost.firstChild.nextSibling, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 1 }],
            ]
          : [
              // \n[abc\n<div>d]ef
              [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: firstTextInChildDiv, endOffset: 1 }],
            ],
        "should return a range ends at start of the child <div>"
      );
    }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`);

    promise_test(async t => {
      testUtils.setupEditingHost(initialInnerHTML);
      const firstTextInChildDiv = editingHost.querySelector("div").firstChild;
      await runReplacingTest(
        t, testUtils, initialInnerHTML,
        `${lineBreak}<div id="child">Xef<br>ghi</div>`,
        "should not unwrap the first line of the child <div>",
        lineBreakIsBR
          ? [
              // <br>[abc<br><div>d]ef
              [{ startContainer: editingHost.firstChild.nextSibling, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 1 }],
            ]
          : [
              // \n[abc\n<div>d]ef
              [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: firstTextInChildDiv, endOffset: 1 }],
            ],
        "should return a range ends at start of the child <div>"
      );
    }, `${insertTextMethod} at ${initialInnerHTML.replaceAll("\n", "\\n")}`);
  })();

  (() => {
    const initialInnerHTML =
      `${lineBreak}${selStart}${lineBreak}<div id="child">d]ef<br>ghi</div>`;
    promise_test(async t => {
      testUtils.setupEditingHost(initialInnerHTML);
      const firstTextInChildDiv = editingHost.querySelector("div").firstChild;
      await runDeleteTest(
        t, testUtils, initialInnerHTML,
        `${lineBreak}<div id="child">ef<br>ghi</div>`,
        "should delete only the preceding empty line of the child <div> and selected content in the <div>",
        `${lineBreak}<div id="child">Xef<br>ghi</div>`,
        "should insert text into the child <div>",
        lineBreakIsBR
          ? [
              // <br>{<br><div>d]ef
              [{ startContainer: editingHost, startOffset: 1, endContainer: firstTextInChildDiv, endOffset: 1 }],
            ]
          : [
              // \n[\n<div>d]ef
              [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: firstTextInChildDiv, endOffset: 1 }],
            ],
        "should return a range ends at start of the child <div>"
      );
    }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`);

    promise_test(async t => {
      testUtils.setupEditingHost(initialInnerHTML);
      const firstTextInChildDiv = editingHost.querySelector("div").firstChild;
      await runReplacingTest(
        t, testUtils, initialInnerHTML,
        `${lineBreak}<div id="child">Xef<br>ghi</div>`,
        "should not unwrap the first line of the child <div>",
        lineBreakIsBR
          ? [
              // <br>{<br><div>d]ef
              [{ startContainer: editingHost, startOffset: 1, endContainer: firstTextInChildDiv, endOffset: 1 }],
            ]
          : [
              // \n[\n<div>d]ef
              [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: firstTextInChildDiv, endOffset: 1 }],
            ],
        "should return a range ends at start of the child <div>"
      );
    }, `${insertTextMethod} at ${initialInnerHTML.replaceAll("\n", "\\n")}`);
  })();

  (() => {
    const initialInnerHTML =
      `${selStart}${lineBreak}${lineBreak}<div id="child">d]ef<br>ghi</div>`;
    promise_test(async t => {
      testUtils.setupEditingHost(initialInnerHTML);
      const firstTextInChildDiv = editingHost.querySelector("div").firstChild;
      await runDeleteTest(
        t, testUtils, initialInnerHTML,
        `<div id="child">ef<br>ghi</div>`,
        "should delete only the preceding empty line of the child <div> and selected content in the <div>",
        `<div id="child">Xef<br>ghi</div>`,
        "should insert text into the child <div>",
        lineBreakIsBR
          ? [
              // {<br><br><div>d]ef
              [{ startContainer: editingHost, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 1 }],
            ]
          : [
              // [\n\n<div>d]ef
              [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 1 }],
            ],
        "should return a range ends at start of the child <div>"
      );
    }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`);

    promise_test(async t => {
      testUtils.setupEditingHost(initialInnerHTML);
      const firstTextInChildDiv = editingHost.querySelector("div").firstChild;
      await runReplacingTest(
        t, testUtils, initialInnerHTML,
        `<div id="child">Xef<br>ghi</div>`,
        "should not unwrap the first line of the child <div>",
        lineBreakIsBR
          ? [
              // {<br><br><div>d]ef
              [{ startContainer: editingHost, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 1 }],
            ]
          : [
              // [\n\n<div>d]ef
              [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 1 }],
            ],
        "should return a range ends at start of the child <div>"
      );
    }, `${insertTextMethod} at ${initialInnerHTML.replaceAll("\n", "\\n")}`);
  })();

  (() => {
    const initialInnerHTML =
      `[abc${lineBreak}${lineBreak}<div id="child">d]ef<br>ghi</div>`;
    promise_test(async t => {
      testUtils.setupEditingHost(initialInnerHTML);
      const firstTextInChildDiv = editingHost.querySelector("div").firstChild;
      await runDeleteTest(
        t, testUtils, initialInnerHTML,
        `<div id="child">ef<br>ghi</div>`,
        "should delete only the preceding empty line of the child <div> and selected content in the <div>",
        `<div id="child">Xef<br>ghi</div>`,
        "should insert text into the child <div>",
        lineBreakIsBR
          ? [
              // [abc<br><br><div>d]ef
              [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 1 }],
            ]
          : [
              // [abc\n\n}<div>d]ef
              [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 1 }],
            ],
        "should return a range ends at start of the child <div>"
      );
    }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`);

    promise_test(async t => {
      testUtils.setupEditingHost(initialInnerHTML);
      const firstTextInChildDiv = editingHost.querySelector("div").firstChild;
      await runReplacingTest(
        t, testUtils, initialInnerHTML,
        `<div id="child">Xef<br>ghi</div>`,
        "should not unwrap the first line of the child <div>",
        lineBreakIsBR
          ? [
              // [abc<br><div>d]ef
              [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 1 }],
            ]
          : [
              // [abc\n<div>d]ef
              [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 1 }],
            ],
        "should return a range ends at start of the child <div>"
      );
    }, `${insertTextMethod} at ${initialInnerHTML.replaceAll("\n", "\\n")}`);
  })();

  (function test_BackspaceForCollapsedSelection() {
    if (!testBackward) {
      return;
    }
    (() => {
      const initialInnerHTML =
        `abc${lineBreak}${lineBreak}<div id="child">[]def<br>ghi</div>`;
      promise_test(async t => {
        testUtils.setupEditingHost(initialInnerHTML);
        await runDeleteTest(
          t, testUtils, initialInnerHTML,
          [
            `abc${lineBreak}<div id="child">def<br>ghi</div>`,
            `abc<div id="child">def<br>ghi</div>`,
          ],
          "should delete only the preceding empty line of the child <div>",
          [
            `abc${lineBreak}<div id="child">Xdef<br>ghi</div>`,
            `abc<div id="child">Xdef<br>ghi</div>`,
          ],
          "should insert text into the child <div>",
          lineBreakIsBR
            ? [
                // abc<br>{<br>}<div>
                [{ startContainer: editingHost, startOffset: 2, endContainer: editingHost, endOffset: 3 }],
                // abc{<br><br>}<div>
                [{ startContainer: editingHost, startOffset: 1, endContainer: editingHost, endOffset: 3 }],
                // abc[<br><br>}<div>
                [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: editingHost, endOffset: 3 }],
              ]
            : [
                // abc\n[\n}<div>
                [{ startContainer: editingHost.firstChild, startOffset: "abc\n".length, endContainer: editingHost, endOffset: 1 }],
                // abc[\n\n}<div>
                [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: editingHost, endOffset: 1 }],
                // abc\n[\n]<div>
                [{ startContainer: editingHost.firstChild, startOffset: "abc\n".length, endContainer: editingHost.firstChild, endOffset: "abc\n\n".length }],
                // abc[\n\n]<div>
                [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: editingHost.firstChild, endOffset: "abc\n\n".length }],
              ],
          "should return a range before the child <div>"
        );
      }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`);
    })();

    (() => {
      const initialInnerHTML =
        `${lineBreak}${lineBreak}<div id="child">[]def<br>ghi</div>`;
      promise_test(async t => {
        testUtils.setupEditingHost(initialInnerHTML);
        await runDeleteTest(
          t, testUtils, initialInnerHTML,
          `${lineBreak}<div id="child">def<br>ghi</div>`,
          "should delete only the preceding empty line of the child <div>",
          `${lineBreak}<div id="child">Xdef<br>ghi</div>`,
          "should insert text into the child <div>",
          lineBreakIsBR
            ? [
                // <br>{<br>}<div>
                [{ startContainer: editingHost, startOffset: 1, endContainer: editingHost, endOffset: 2 }],
              ]
            : [
                // \n[\n}<div>
                [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: editingHost, endOffset: 1 }],
                // \n[\n]<div>
                [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: editingHost.firstChild, endOffset: "\n\n".length }],
              ],
          "should return a range before the child <div>"
        );
      }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`);
    })();

    (() => {
      const initialInnerHTML =
        `${lineBreak}<div id="child">[]def<br>ghi</div>`;
      promise_test(async t => {
        testUtils.setupEditingHost(initialInnerHTML);
        await runDeleteTest(
          t, testUtils, initialInnerHTML,
          `<div id="child">def<br>ghi</div>`,
          "should delete only the preceding empty line of the child <div>",
          `<div id="child">Xdef<br>ghi</div>`,
          "should insert text into the child <div>",
          lineBreakIsBR
            ? [
                // {<br>}<div>
                [{ startContainer: editingHost, startOffset: 0, endContainer: editingHost, endOffset: 1 }],
              ]
            : [
                // {\n}<div>
                [{ startContainer: editingHost, startOffset: 0, endContainer: editingHost, endOffset: 1 }],
                // [\n}<div>
                [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: editingHost, endOffset: 1 }],
                // [\n]<div>
                [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: editingHost.firstChild, endOffset: "\n".length }],
              ],
          "should return a range before the child <div>"
        );
      }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`);
    })();

    (() => {
      const initialInnerHTML =
        `<b>abc${lineBreak}${lineBreak}</b></b><div id="child">[]def<br>ghi</div>`;
      promise_test(async t => {
        testUtils.setupEditingHost(initialInnerHTML);
        const b = editingHost.querySelector("b");
        await runDeleteTest(
          t, testUtils, initialInnerHTML,
          [
            `<b>abc${lineBreak}</b><div id="child">def<br>ghi</div>`,
            `<b>abc</b><div id="child">def<br>ghi</div>`,
          ],
          "should delete only the preceding empty line of the child <div> (<b> should stay)",
          [
            `<b>abc${lineBreak}</b><div id="child">Xdef<br>ghi</div>`,
            `<b>abc</b><div id="child">Xdef<br>ghi</div>`,
            `<b>abc${lineBreak}</b><div id="child"><b>X</b>def<br>ghi</div>`,
            `<b>abc</b><div id="child"><b>X</b>def<br>ghi</div>`,
          ],
          "should insert text into the child <div> with or without <b>",
          lineBreakIsBR
            ? [
                // <b>abc<br>{<br>}</b><div>
                [{ startContainer: b, startOffset: 2, endContainer: b, endOffset: 3 }],
                // <b>abc{<br><br>}</b><div>
                [{ startContainer: b, startOffset: 1, endContainer: b, endOffset: 3 }],
                // <b>abc[<br><br>}</b><div>
                [{ startContainer: b.firstChild, startOffset: "abc".length, endContainer: b, endOffset: 3 }],
              ]
            : [
                // <b>abc\n[\n}</b><div>
                [{ startContainer: b.firstChild, startOffset: "abc\n".length, endContainer: b, endOffset: 1 }],
                // <b>abc[\n\n}</b><div>
                [{ startContainer: b.firstChild, startOffset: "abc".length, endContainer: b, endOffset: 1 }],
                // <b>abc\n[\n]</b><div>
                [{ startContainer: b.firstChild, startOffset: "abc\n".length, endContainer: b.firstChild, endOffset: "abc\n\n".length }],
                // <b>abc[\n\n]</b><div>
                [{ startContainer: b.firstChild, startOffset: "abc".length, endContainer: b.firstChild, endOffset: "abc\n\n".length }],
              ],
          "should return a range before the child <div>"
        );
      }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`);
    })();

    (() => {
      const initialInnerHTML =
        `<b>${lineBreak}</b><div id="child">[]def<br>ghi</div>`;
      promise_test(async t => {
        testUtils.setupEditingHost(initialInnerHTML);
        await runDeleteTest(
          t, testUtils, initialInnerHTML,
          `<div id="child">def<br>ghi</div>`,
          "should delete only the preceding empty line (including the <b>) of the child <div>",
          [
            `<div id="child">Xdef<br>ghi</div>`,
            `<div id="child"><b>X</b>def<br>ghi</div>`,
          ],
          "should insert text into the child <div> with or without <b>",
          [
                // {<b><br></b>}<div> or {<b>\n</b>}<div>
                [{ startContainer: editingHost, startOffset: 0, endContainer: editingHost, endOffset: 1 }],
          ],
          "should return a range before the child <div>"
        );
      }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`);
    })();
  })();

  (function test_ForwardDeleteForCollapsedSelection() {
    if (testBackward) {
      return;
    }
    (() => {
      const initialInnerHTML =
        `abc${lineBreak}${selCollapsed}${lineBreak}<div id="child">def<br>ghi</div>`;
      promise_test(async t => {
        testUtils.setupEditingHost(initialInnerHTML);
        await runDeleteTest(
          t, testUtils, initialInnerHTML,
          [
            `abc${lineBreak}<div id="child">def<br>ghi</div>`,
            `abc<div id="child">def<br>ghi</div>`,
          ],
          "should delete only the preceding empty line of the child <div>",
          [
            `abc${lineBreak}<div id="child">Xdef<br>ghi</div>`,
            `abc<div id="child">Xdef<br>ghi</div>`,
          ],
          "should insert text into the child <div>",
          lineBreakIsBR
            ? [
                // abc<br>{<br>}<div>
                [{ startContainer: editingHost, startOffset: 2, endContainer: editingHost, endOffset: 3 }],
                // abc{<br><br>}<div>
                [{ startContainer: editingHost, startOffset: 1, endContainer: editingHost, endOffset: 3 }],
                // abc[<br><br>}<div>
                [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: editingHost, endOffset: 3 }],
              ]
            : [
                // abc\n[\n}<div>
                [{ startContainer: editingHost.firstChild, startOffset: "abc\n".length, endContainer: editingHost, endOffset: 1 }],
                // abc[\n\n}<div>
                [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: editingHost, endOffset: 1 }],
                // abc\n[\n]<div>
                [{ startContainer: editingHost.firstChild, startOffset: "abc\n".length, endContainer: editingHost.firstChild, endOffset: "abc\n\n".length }],
                // abc[\n\n]<div>
                [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: editingHost.firstChild, endOffset: "abc\n\n".length }],
              ],
          "should return a range before the child <div>"
        );
      }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`);
    })();

    (() => {
      const initialInnerHTML =
        `${lineBreak}${selCollapsed}${lineBreak}<div id="child">def<br>ghi</div>`;
      promise_test(async t => {
        testUtils.setupEditingHost(initialInnerHTML);
        await runDeleteTest(
          t, testUtils, initialInnerHTML,
          `${lineBreak}<div id="child">def<br>ghi</div>`,
          "should delete only the preceding empty line of the child <div>",
          `${lineBreak}<div id="child">Xdef<br>ghi</div>`,
          "should insert text into the child <div>",
          lineBreakIsBR
            ? [
                // <br>{<br>}<div>
                [{ startContainer: editingHost, startOffset: 1, endContainer: editingHost, endOffset: 2 }],
              ]
            : [
                // \n[\n}<div>
                [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: editingHost, endOffset: 1 }],
                // \n[\n]<div>
                [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: editingHost.firstChild, endOffset: "\n\n".length }],
              ],
          "should return a range before the child <div>"
        );
      }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`);
    })();

    (() => {
      const initialInnerHTML =
        `${selCollapsed}${lineBreak}<div id="child">def<br>ghi</div>`;
      promise_test(async t => {
        testUtils.setupEditingHost(initialInnerHTML);
        await runDeleteTest(
          t, testUtils, initialInnerHTML,
          `<div id="child">def<br>ghi</div>`,
          "should delete only the preceding empty line of the child <div>",
          `<div id="child">Xdef<br>ghi</div>`,
          "should insert text into the child <div>",
          lineBreakIsBR
            ? [
                // {<br>}<div>
                [{ startContainer: editingHost, startOffset: 0, endContainer: editingHost, endOffset: 1 }],
              ]
            : [
                // {\n}<div>
                [{ startContainer: editingHost, startOffset: 0, endContainer: editingHost, endOffset: 1 }],
                // [\n}<div>
                [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: editingHost, endOffset: 1 }],
                // [\n]<div>
                [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: editingHost.firstChild, endOffset: "\n".length }],
              ],
          "should return a range before the child <div>"
        );
      }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`);
    })();

    (() => {
      const initialInnerHTML =
        `<b>abc${lineBreak}${selCollapsed}${lineBreak}</b></b><div id="child">def<br>ghi</div>`;
      promise_test(async t => {
        testUtils.setupEditingHost(initialInnerHTML);
        const b = editingHost.querySelector("b");
        await runDeleteTest(
          t, testUtils, initialInnerHTML,
          [
            `<b>abc${lineBreak}</b><div id="child">def<br>ghi</div>`,
            `<b>abc</b><div id="child">def<br>ghi</div>`,
          ],
          "should delete only the preceding empty line of the child <div> (<b> should stay)",
          [
            `<b>abc${lineBreak}</b><div id="child">Xdef<br>ghi</div>`,
            `<b>abc</b><div id="child">Xdef<br>ghi</div>`,
            `<b>abc${lineBreak}</b><div id="child"><b>X</b>def<br>ghi</div>`,
            `<b>abc</b><div id="child"><b>X</b>def<br>ghi</div>`,
          ],
          "should insert text into the child <div> with or without <b>",
          lineBreakIsBR
            ? [
                // <b>abc<br>{<br>}</b><div>
                [{ startContainer: b, startOffset: 2, endContainer: b, endOffset: 3 }],
                // <b>abc{<br><br>}</b><div>
                [{ startContainer: b, startOffset: 1, endContainer: b, endOffset: 3 }],
                // <b>abc[<br><br>}</b><div>
                [{ startContainer: b.firstChild, startOffset: "abc".length, endContainer: b, endOffset: 3 }],
              ]
            : [
                // <b>abc\n[\n}</b><div>
                [{ startContainer: b.firstChild, startOffset: "abc\n".length, endContainer: b, endOffset: 1 }],
                // <b>abc[\n\n}</b><div>
                [{ startContainer: b.firstChild, startOffset: "abc".length, endContainer: b, endOffset: 1 }],
                // <b>abc\n[\n]</b><div>
                [{ startContainer: b.firstChild, startOffset: "abc\n".length, endContainer: b.firstChild, endOffset: "abc\n\n".length }],
                // <b>abc[\n\n]</b><div>
                [{ startContainer: b.firstChild, startOffset: "abc".length, endContainer: b.firstChild, endOffset: "abc\n\n".length }],
              ],
          "should return a range before the child <div>"
        );
      }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`);
    })();

    (() => {
      const initialInnerHTML =
        `<b>${selCollapsed}${lineBreak}</b><div id="child">def<br>ghi</div>`;
      promise_test(async t => {
        testUtils.setupEditingHost(initialInnerHTML);
        await runDeleteTest(
          t, testUtils, initialInnerHTML,
          `<div id="child">def<br>ghi</div>`,
          "should delete only the preceding empty line (including the <b>) of the child <div>",
          [
            `<div id="child">Xdef<br>ghi</div>`,
            `<div id="child"><b>X</b>def<br>ghi</div>`,
          ],
          "should insert text into the child <div> with or without <b>",
          [
                // {<b><br></b>}<div> or {<b>\n</b>}<div>
                [{ startContainer: editingHost, startOffset: 0, endContainer: editingHost, endOffset: 1 }],
          ],
          "should return a range before the child <div>"
        );
      }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`);
    })();
  })();
}, {once: true});
</script>
</head>
<body><div contenteditable></div></body>
</html>