summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/editing
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:43:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:43:14 +0000
commit8dd16259287f58f9273002717ec4d27e97127719 (patch)
tree3863e62a53829a84037444beab3abd4ed9dfc7d0 /testing/web-platform/tests/editing
parentReleasing progress-linux version 126.0.1-1~progress7.99u1. (diff)
downloadfirefox-8dd16259287f58f9273002717ec4d27e97127719.tar.xz
firefox-8dd16259287f58f9273002717ec4d27e97127719.zip
Merging upstream version 127.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/editing')
-rw-r--r--testing/web-platform/tests/editing/data/delete.js6
-rw-r--r--testing/web-platform/tests/editing/data/forwarddelete.js26
-rw-r--r--testing/web-platform/tests/editing/edit-context/WEB_FEATURES.yml3
-rw-r--r--testing/web-platform/tests/editing/include/editor-test-utils.js75
-rw-r--r--testing/web-platform/tests/editing/other/delete-without-unwrapping-first-line-of-child-block.html1020
5 files changed, 1114 insertions, 16 deletions
diff --git a/testing/web-platform/tests/editing/data/delete.js b/testing/web-platform/tests/editing/data/delete.js
index 131c99b1d5..c4d1225ef3 100644
--- a/testing/web-platform/tests/editing/data/delete.js
+++ b/testing/web-platform/tests/editing/data/delete.js
@@ -2044,12 +2044,12 @@ var browserTests = [
{"defaultparagraphseparator":[false,false,"div",false,false,"p"],"delete":[false,false,"",false,false,""]}],
["foo<br><br>{<p>]bar</p>",
[["defaultparagraphseparator","div"],["delete",""]],
- "foo<br>{}bar",
+ "foo<br><p>bar</p>",
[true,true],
{"defaultparagraphseparator":[false,false,"p",false,false,"div"],"delete":[false,false,"",false,false,""]}],
["foo<br><br>{<p>]bar</p>",
[["defaultparagraphseparator","p"],["delete",""]],
- "foo<br>{}bar",
+ "foo<br><p>bar</p>",
[true,true],
{"defaultparagraphseparator":[false,false,"div",false,false,"p"],"delete":[false,false,"",false,false,""]}],
["<p>foo<br>{</p><p>}bar</p>",
@@ -2359,7 +2359,7 @@ var browserTests = [
{"delete":[false,false,"",false,false,""]}],
["<ol><li>foo</ol>[bar<ol><li>]baz</ol>",
[["delete",""]],
- "<ol><li>foo</li></ol>{}baz",
+ "<ol><li>foo</li></ol><ol><li>baz</li></ol>",
[true],
{"delete":[false,false,"",false,false,""]}],
["<ol><li>foo</ol><p>[bar<ol><li>]baz</ol>",
diff --git a/testing/web-platform/tests/editing/data/forwarddelete.js b/testing/web-platform/tests/editing/data/forwarddelete.js
index ea590a4fbb..a881fb6ccf 100644
--- a/testing/web-platform/tests/editing/data/forwarddelete.js
+++ b/testing/web-platform/tests/editing/data/forwarddelete.js
@@ -2009,12 +2009,12 @@ var browserTests = [
{"defaultparagraphseparator":[false,false,"div",false,false,"p"],"forwarddelete":[false,false,"",false,false,""]}],
["foo<br><br>{<p>]bar</p>",
[["defaultparagraphseparator","div"],["forwarddelete",""]],
- "foo<br>{}bar",
+ "foo<br><p>bar</p>",
[true,true],
{"defaultparagraphseparator":[false,false,"p",false,false,"div"],"forwarddelete":[false,false,"",false,false,""]}],
["foo<br><br>{<p>]bar</p>",
[["defaultparagraphseparator","p"],["forwarddelete",""]],
- "foo<br>{}bar",
+ "foo<br><p>bar</p>",
[true,true],
{"defaultparagraphseparator":[false,false,"div",false,false,"p"],"forwarddelete":[false,false,"",false,false,""]}],
["<p>foo<br>{</p><p>}bar</p>",
@@ -2184,7 +2184,7 @@ var browserTests = [
{"forwarddelete":[false,false,"",false,false,""]}],
["<ol><li>foo</ol>{}<br><ol><li>bar</ol>",
[["forwarddelete",""]],
- "<ol><li>foo</li></ol>{}bar",
+ "<ol><li>foo</li></ol><ol><li>bar</li></ol>",
[true],
{"forwarddelete":[false,false,"",false,false,""]}],
["<ol><li>foo</ol><p>{}<br></p><ol><li>bar</ol>",
@@ -2199,22 +2199,22 @@ var browserTests = [
{"forwarddelete":[false,false,"",false,false,""]}],
["<ol id=a><li>foo</ol>{}<br><ol><li>bar</ol>",
[["forwarddelete",""]],
- "<ol id=\"a\"><li>foo</li></ol>{}bar",
+ "<ol id=\"a\"><li>foo</li></ol><ol><li>bar</li></ol>",
[true],
{"forwarddelete":[false,false,"",false,false,""]}],
["<ol><li>foo</ol>{}<br><ol id=b><li>bar</ol>",
[["forwarddelete",""]],
- "<ol><li>foo</li></ol>{}bar",
+ "<ol><li>foo</li></ol><ol id=\"b\"><li>bar</li></ol>",
[true],
{"forwarddelete":[false,false,"",false,false,""]}],
["<ol id=a><li>foo</ol>{}<br><ol id=b><li>bar</ol>",
[["forwarddelete",""]],
- "<ol id=\"a\"><li>foo</li></ol>{}bar",
+ "<ol id=\"a\"><li>foo</li></ol><ol id=\"b\"><li>bar</li></ol>",
[true],
{"forwarddelete":[false,false,"",false,false,""]}],
["<ol class=a><li>foo</ol>{}<br><ol class=b><li>bar</ol>",
[["forwarddelete",""]],
- "<ol class=\"a\"><li>foo</li></ol>{}bar",
+ "<ol class=\"a\"><li>foo</li></ol><ol class=\"b\"><li>bar</li></ol>",
[true],
{"forwarddelete":[false,false,"",false,false,""]}],
["<ol><ol><li>foo</ol><li>{}<br><ol><li>bar</ol></ol>",
@@ -2259,7 +2259,7 @@ var browserTests = [
{"forwarddelete":[false,false,"",false,false,""]}],
["<ol><li>foo</ol>[bar<ol><li>]baz</ol>",
[["forwarddelete",""]],
- "<ol><li>foo</li></ol>{}baz",
+ "<ol><li>foo</li></ol><ol><li>baz</li></ol>",
[true],
{"forwarddelete":[false,false,"",false,false,""]}],
["<ol><li>foo</ol><p>[bar<ol><li>]baz</ol>",
@@ -2269,12 +2269,12 @@ var browserTests = [
{"forwarddelete":[false,false,"",false,false,""]}],
["<ol><li>foo</ol><p>[bar<ol><li><p>]baz</ol>",
[["defaultparagraphseparator","div"],["forwarddelete",""]],
- "<ol><li>foo</li></ol><p>{}baz</p>",
+ "<ol><li>foo</li></ol><ol><li><p>{}baz</p></li></ol>",
[true,true],
{"defaultparagraphseparator":[false,false,"p",false,false,"div"],"forwarddelete":[false,false,"",false,false,""]}],
["<ol><li>foo</ol><p>[bar<ol><li><p>]baz</ol>",
[["defaultparagraphseparator","p"],["forwarddelete",""]],
- "<ol><li>foo</li></ol><p>{}baz</p>",
+ "<ol><li>foo</li></ol><ol><li><p>{}baz</p></li></ol>",
[true,true],
{"defaultparagraphseparator":[false,false,"div",false,false,"p"],"forwarddelete":[false,false,"",false,false,""]}],
["<ol><li>foo</ol><ol><li>[]bar</ol>",
@@ -2289,7 +2289,7 @@ var browserTests = [
{"forwarddelete":[false,false,"",false,false,""]}],
["<ul><li>foo</ul>{}<br><ul><li>bar</ul>",
[["forwarddelete",""]],
- "<ul><li>foo</li></ul>{}bar",
+ "<ul><li>foo</li></ul><ul><li>bar</li></ul>",
[true],
{"forwarddelete":[false,false,"",false,false,""]}],
["<ul><li>foo</ul><p>{}<br></p><ul><li>bar</ul>",
@@ -2304,7 +2304,7 @@ var browserTests = [
{"forwarddelete":[false,false,"",false,false,""]}],
["<ol><li>foo</ol>{}<br><ul><li>bar</ul>",
[["forwarddelete",""]],
- "<ol><li>foo</li></ol>{}bar",
+ "<ol><li>foo</li></ol><ul><li>bar</li></ul>",
[true],
{"forwarddelete":[false,false,"",false,false,""]}],
["<ol><li>foo</ol><p>{}<br></p><ul><li>bar</ul>",
@@ -2314,7 +2314,7 @@ var browserTests = [
{"forwarddelete":[false,false,"",false,false,""]}],
["<ul><li>foo</ul>{}<br><ol><li>bar</ol>",
[["forwarddelete",""]],
- "<ul><li>foo</li></ul>{}bar",
+ "<ul><li>foo</li></ul><ol><li>bar</li></ol>",
[true],
{"forwarddelete":[false,false,"",false,false,""]}],
["<ul><li>foo</ul><p>{}<br></p><ol><li>bar</ol>",
diff --git a/testing/web-platform/tests/editing/edit-context/WEB_FEATURES.yml b/testing/web-platform/tests/editing/edit-context/WEB_FEATURES.yml
new file mode 100644
index 0000000000..284280066a
--- /dev/null
+++ b/testing/web-platform/tests/editing/edit-context/WEB_FEATURES.yml
@@ -0,0 +1,3 @@
+features:
+- name: edit-context
+ files: "**"
diff --git a/testing/web-platform/tests/editing/include/editor-test-utils.js b/testing/web-platform/tests/editing/include/editor-test-utils.js
index d0d50d22a6..b180f3343f 100644
--- a/testing/web-platform/tests/editing/include/editor-test-utils.js
+++ b/testing/web-platform/tests/editing/include/editor-test-utils.js
@@ -424,4 +424,79 @@ class EditorTestUtils {
);
}
}
+
+ static getRangeArrayDescription(arrayOfRanges) {
+ if (arrayOfRanges === null) {
+ return "null";
+ }
+ if (arrayOfRanges === undefined) {
+ return "undefined";
+ }
+ if (!Array.isArray(arrayOfRanges)) {
+ return "Unknown Object";
+ }
+ if (arrayOfRanges.length === 0) {
+ return "[]";
+ }
+ let result = "";
+ for (let range of arrayOfRanges) {
+ if (result === "") {
+ result = "[";
+ } else {
+ result += ",";
+ }
+ result += `{${EditorTestUtils.getRangeDescription(range)}}`;
+ }
+ result += "]";
+ return result;
+ }
+
+ static getNodeDescription(node) {
+ if (!node) {
+ return "null";
+ }
+ switch (node.nodeType) {
+ case Node.TEXT_NODE:
+ case Node.COMMENT_NODE:
+ case Node.CDATA_SECTION_NODE:
+ return `${node.nodeName} "${node.data.replaceAll("\n", "\\\\n")}"`;
+ case Node.ELEMENT_NODE:
+ return `<${node.nodeName.toLowerCase()}${
+ node.hasAttribute("id") ? ` id="${node.getAttribute("id")}"` : ""
+ }${
+ node.hasAttribute("class") ? ` class="${node.getAttribute("class")}"` : ""
+ }${
+ node.hasAttribute("contenteditable")
+ ? ` contenteditable="${node.getAttribute("contenteditable")}"`
+ : ""
+ }${
+ node.inert ? ` inert` : ""
+ }${
+ node.hidden ? ` hidden` : ""
+ }${
+ node.readonly ? ` readonly` : ""
+ }${
+ node.disabled ? ` disabled` : ""
+ }>`;
+ default:
+ return `${node.nodeName}`;
+ }
+ }
+
+ static getRangeDescription(range) {
+ if (range === null) {
+ return "null";
+ }
+ if (range === undefined) {
+ return "undefined";
+ }
+ return range.startContainer == range.endContainer &&
+ range.startOffset == range.endOffset
+ ? `(${EditorTestUtils.getNodeDescription(range.startContainer)}, ${range.startOffset})`
+ : `(${EditorTestUtils.getNodeDescription(range.startContainer)}, ${
+ range.startOffset
+ }) - (${EditorTestUtils.getNodeDescription(range.endContainer)}, ${range.endOffset})`;
+ }
+
+
}
diff --git a/testing/web-platform/tests/editing/other/delete-without-unwrapping-first-line-of-child-block.html b/testing/web-platform/tests/editing/other/delete-without-unwrapping-first-line-of-child-block.html
new file mode 100644
index 0000000000..99f8f05888
--- /dev/null
+++ b/testing/web-platform/tests/editing/other/delete-without-unwrapping-first-line-of-child-block.html
@@ -0,0 +1,1020 @@
+<!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>