Skip to content

Commit 790aab4

Browse files
masayuki-nakanomoz-wptsync-bot
authored andcommitted
Add WPTs to check the editing behavior when contenteditable="plaintext-only" and contenteditable="true" are nested
Adding the tests for the conclusion of <w3c/editing#470>. If `contenteditable="true"` (`=""`) and `contenteditable="plaintext-only"` without `contenteditable="false"`, the outermost editing host should consider whether it's a richtext editor or a plaintext editor. Differential Revision: https://phabricator.services.mozilla.com/D239868 bugzilla-url: https://bugzilla.mozilla.org/show_bug.cgi?id=1921171 gecko-commit: ecc241ac8ebc8b436b9ca4d9a0474b78b960107e gecko-reviewers: smaug
1 parent 5bbb8c3 commit 790aab4

3 files changed

+341
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="timeout" content="long">
6+
<title>Testing contenteditable=plaintext-only which is nested with contenteditable=true</title>
7+
<script src="/resources/testharness.js"></script>
8+
<script src="/resources/testharnessreport.js"></script>
9+
<script src="/resources/testdriver.js"></script>
10+
<script src="/resources/testdriver-vendor.js"></script>
11+
<script src="/resources/testdriver-actions.js"></script>
12+
<script src="../include/editor-test-utils.js"></script>
13+
<script>
14+
"use strict";
15+
16+
addEventListener("load", () => {
17+
const editingHost = document.createElement("div");
18+
editingHost.setAttribute("contenteditable", "");
19+
document.body.appendChild(editingHost);
20+
editingHost.focus();
21+
const utils = new EditorTestUtils(editingHost);
22+
for (const trueOrEmpty of ["", "true"]) {
23+
for (const explicitlySetFocus of [false, true]) {
24+
for (const data of [
25+
{
26+
desc: `contenteditable="plaintext-only" in contenteditable="${trueOrEmpty}" should support style edit`,
27+
init: `<div contenteditable="plaintext-only">[abc]</div>`,
28+
run: function () {
29+
document.execCommand("bold");
30+
},
31+
expected: `<div contenteditable="plaintext-only"><b>abc</b></div>`
32+
},
33+
{
34+
desc: `contenteditable="plaintext-only" in contenteditable="${
35+
trueOrEmpty
36+
}" and contenteditable="false" should not support style edit`,
37+
init: `<div contenteditable="false"><div contenteditable="plaintext-only">[abc]</div></div>`,
38+
run: function () {
39+
document.execCommand("bold");
40+
},
41+
expected: `<div contenteditable="false"><div contenteditable="plaintext-only">abc</div></div>`,
42+
},
43+
{
44+
desc: `contenteditable="plaintext-only" in contenteditable="${trueOrEmpty}" should insert paragraph at typing Enter`,
45+
init: `<div contenteditable="plaintext-only"><p>a[b]c</p></div>`,
46+
run: function () {
47+
return utils.sendEnterKey();
48+
},
49+
expected: `<div contenteditable="plaintext-only"><p>a</p><p>c</p></div>`
50+
},
51+
{
52+
desc: `contenteditable="plaintext-only" in contenteditable="${
53+
trueOrEmpty
54+
}" and contenteditable="false" should insert line break at typing Enter`,
55+
init: `<div contenteditable="false"><div contenteditable="plaintext-only"><p>a[b]c</p></div></div>`,
56+
run: function () {
57+
return utils.sendEnterKey();
58+
},
59+
expected: `<div contenteditable="false"><div contenteditable="plaintext-only"><p>a<br>c</p></div></div>`,
60+
},
61+
{
62+
desc: `styling start boundary of contenteditable="plaintext-only" in contenteditable="${
63+
trueOrEmpty
64+
}" should apply the style to entire the range`,
65+
init: `A[B<div contenteditable="plaintext-only">C]D</div>EF`,
66+
run: function () {
67+
document.execCommand("bold");
68+
},
69+
expected: `A<b>B</b><div contenteditable="plaintext-only"><b>C</b>D</div>EF`,
70+
},
71+
{
72+
desc: `styling end boundary of contenteditable="plaintext-only" in contenteditable="${
73+
trueOrEmpty
74+
}" should apply the style to entire the range`,
75+
init: `AB<div contenteditable="plaintext-only">C[D</div>E]F`,
76+
run: function () {
77+
document.execCommand("bold");
78+
},
79+
expected: `AB<div contenteditable="plaintext-only">C<b>D</b></div><b>E</b>F`,
80+
},
81+
{
82+
desc: `even after moving selection into contenteditable="plaintext-only" in contenteditable="${
83+
trueOrEmpty
84+
}" and contenteditable="false" from parent editing host should not support style edit`,
85+
init: `A[]B<div contenteditable="false"><div contenteditable="plaintext-only">CD</div></div>EF`,
86+
run: function () {
87+
getSelection().selectAllChildren(editingHost.querySelector("div[contenteditable=plaintext-only]"));
88+
document.execCommand("bold");
89+
},
90+
expected: `AB<div contenteditable="false"><div contenteditable="plaintext-only">CD</div></div>EF`,
91+
},
92+
]) {
93+
promise_test(async () => {
94+
editingHost.setAttribute("contenteditable", trueOrEmpty);
95+
utils.setupEditingHost(data.init);
96+
if (explicitlySetFocus) {
97+
editingHost.querySelector("[contenteditable=plaintext-only]").focus();
98+
}
99+
await data.run();
100+
assert_equals(editingHost.outerHTML, `<div contenteditable="${trueOrEmpty}">${data.expected}</div>`);
101+
}, data.desc + (explicitlySetFocus ? " (explicitly setting focus to the nested one)" : ""));
102+
}
103+
104+
for (const data of [
105+
{
106+
desc: `contenteditable="${trueOrEmpty}" in contenteditable="plaintext-only" should not support style edit`,
107+
init: `<div contenteditable="${trueOrEmpty}">[abc]</div>`,
108+
run: function () {
109+
document.execCommand("bold");
110+
},
111+
expected: `<div contenteditable="${trueOrEmpty}">abc</div>`
112+
},
113+
{
114+
desc: `contenteditable="${
115+
trueOrEmpty
116+
}" in contenteditable="plaintext-only" and contenteditable="false" should support style edit`,
117+
init: `<div contenteditable="false"><div contenteditable="${trueOrEmpty}">[abc]</div></div>`,
118+
run: function () {
119+
document.execCommand("bold");
120+
},
121+
expected: `<div contenteditable="false"><div contenteditable="${trueOrEmpty}"><b>abc</b></div></div>`,
122+
},
123+
{
124+
desc: `contenteditable="${trueOrEmpty}" in contenteditable="plaintext-only" should insert line break at typing Enter`,
125+
init: `<div contenteditable="${trueOrEmpty}"><p>a[b]c</p></div>`,
126+
run: function () {
127+
return utils.sendEnterKey();
128+
},
129+
expected: `<div contenteditable="${trueOrEmpty}"><p>a<br>c</p></div>`
130+
},
131+
{
132+
desc: `contenteditable="${
133+
trueOrEmpty
134+
}" in contenteditable="plaintext-only" and contenteditable="false" should insert paragraph at typing Enter`,
135+
init: `<div contenteditable="false"><div contenteditable="${trueOrEmpty}"><p>a[b]c</p></div></div>`,
136+
run: function () {
137+
return utils.sendEnterKey();
138+
},
139+
expected: `<div contenteditable="false"><div contenteditable="${trueOrEmpty}"><p>a</p><p>c</p></div></div>`,
140+
},
141+
{
142+
desc: `styling start boundary of contenteditable="${
143+
trueOrEmpty
144+
}" in contenteditable="plaintext-only" should not apply the style`,
145+
init: `A[B<div contenteditable="${trueOrEmpty}">C]D</div>EF`,
146+
run: function () {
147+
document.execCommand("bold");
148+
},
149+
expected: `AB<div contenteditable="${trueOrEmpty}">CD</div>EF`,
150+
},
151+
{
152+
desc: `styling end boundary of contenteditable="${
153+
trueOrEmpty
154+
}" in contenteditable="plaintext-only" should not apply the style`,
155+
init: `AB<div contenteditable="${trueOrEmpty}">C[D</div>E]F`,
156+
run: function () {
157+
document.execCommand("bold");
158+
},
159+
expected: `AB<div contenteditable="${trueOrEmpty}">CD</div>EF`,
160+
},
161+
{
162+
desc: `even after moving selection into contenteditable="${
163+
trueOrEmpty
164+
}" in contenteditable="plaintext-only" and contenteditable="false" from parent editing host should support style edit`,
165+
init: `A[]B<div contenteditable="false"><div contenteditable="${trueOrEmpty}">CD</div></div>EF`,
166+
run: function () {
167+
getSelection().selectAllChildren(editingHost.querySelector(`div[contenteditable="${trueOrEmpty}"]`));
168+
document.execCommand("bold");
169+
},
170+
expected: `AB<div contenteditable="false"><div contenteditable="${trueOrEmpty}"><b>CD</b></div></div>EF`,
171+
},
172+
]) {
173+
promise_test(async () => {
174+
editingHost.setAttribute("contenteditable", "plaintext-only");
175+
utils.setupEditingHost(data.init);
176+
if (explicitlySetFocus) {
177+
editingHost.querySelector(`[contenteditable='${trueOrEmpty}']`).focus();
178+
}
179+
await data.run();
180+
assert_equals(editingHost.outerHTML, `<div contenteditable="plaintext-only">${data.expected}</div>`);
181+
}, data.desc + (explicitlySetFocus ? " (explicitly setting focus to the nested one)" : ""));
182+
}
183+
}
184+
}
185+
}, {once: true});
186+
</script>
187+
</head>
188+
<body></body>
189+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="timeout" content="long">
6+
<title>Delete editor in a shadow</title>
7+
<script src="/resources/testharness.js"></script>
8+
<script src="/resources/testharnessreport.js"></script>
9+
<script src="/resources/testdriver.js"></script>
10+
<script src="/resources/testdriver-vendor.js"></script>
11+
<script src="/resources/testdriver-actions.js"></script>
12+
<script src="../include/editor-test-utils.js"></script>
13+
<script>
14+
"use strict";
15+
16+
addEventListener("load", () => {
17+
promise_test(async () => {
18+
const div = document.createElement("div");
19+
div.contentEditable = "true";
20+
div.innerHTML = "<b>abc</b>";
21+
document.body.appendChild(div);
22+
await test_driver.click(div);
23+
getSelection().selectAllChildren(div);
24+
await (new EditorTestUtils(div)).sendCopyShortcutKey();
25+
assert_true(true);
26+
}, `Initializing the clipboard with <b>abc</b>...`);
27+
28+
promise_test(async () => {
29+
const editingHost = document.createElement("div");
30+
document.body.appendChild(editingHost);
31+
editingHost.focus();
32+
const utils = new EditorTestUtils(editingHost);
33+
let lastBeforeInputEvent;
34+
editingHost.addEventListener("beforeinput", event => lastBeforeInputEvent = event);
35+
for (const data of [
36+
{
37+
desc: `pasting in contenteditable="plaintext-only" in contenteditable="true"`,
38+
init: `<div contenteditable="plaintext-only">[ABC]</div>`,
39+
expected: `<div contenteditable="plaintext-only"><b>abc</b></div>`
40+
},
41+
{
42+
desc: `pasting in contenteditable="plaintext-only" in contenteditable="true" and contenteditable="false"`,
43+
init: `<div contenteditable="false"><div contenteditable="plaintext-only">[ABC]</div></div>`,
44+
expected: `<div contenteditable="false"><div contenteditable="plaintext-only">abc</div></div>`,
45+
},
46+
]) {
47+
promise_test(async t => {
48+
editingHost.setAttribute("contenteditable", "true");
49+
utils.setupEditingHost(data.init);
50+
lastBeforeInputEvent = undefined;
51+
await utils.sendPasteShortcutKey();
52+
test(() => {
53+
assert_equals(
54+
lastBeforeInputEvent?.inputType,
55+
"insertFromPaste",
56+
"beforeinput.inputType should be insertFromPaste"
57+
);
58+
assert_equals(lastBeforeInputEvent?.data, null, "beforeinput.data should be null");
59+
assert_true(
60+
String(lastBeforeInputEvent?.dataTransfer?.getData("text/html")).includes("<b>abc</b>"),
61+
"beforeinput.dataTransfer should have the styled text as text/html"
62+
);
63+
}, `${t.name}: beforeinput`);
64+
test(() => {
65+
assert_equals(editingHost.outerHTML, `<div contenteditable="true">${data.expected}</div>`);
66+
}, `${t.name}: innerHTML`);
67+
}, data.desc);
68+
}
69+
for (const data of [
70+
{
71+
desc: `pasting in contenteditable="true" in contenteditable="plaintext-only"`,
72+
init: `<div contenteditable="true">[ABC]</div>`,
73+
expected: `<div contenteditable="true">abc</div>`
74+
},
75+
{
76+
desc: `pasting in contenteditable="true" in contenteditable="plaintext-only" and contenteditable="false"`,
77+
init: `<div contenteditable="false"><div contenteditable="true">[ABC]</div></div>`,
78+
expected: `<div contenteditable="false"><div contenteditable="true"><b>abc</b></div></div>`,
79+
},
80+
]) {
81+
promise_test(async t => {
82+
editingHost.setAttribute("contenteditable", "plaintext-only");
83+
utils.setupEditingHost(data.init);
84+
lastBeforeInputEvent = undefined;
85+
await utils.sendPasteShortcutKey();
86+
test(() => {
87+
assert_equals(
88+
lastBeforeInputEvent?.inputType,
89+
"insertFromPaste",
90+
"beforeinput.inputType should be insertFromPaste"
91+
);
92+
assert_equals(lastBeforeInputEvent?.data, null, "beforeinput.data should be null");
93+
assert_true(
94+
String(lastBeforeInputEvent?.dataTransfer?.getData("text/html")).includes("<b>abc</b>"),
95+
"beforeinput.dataTransfer should have the styled text as text/html"
96+
);
97+
}, `${t.name}: beforeinput`);
98+
test(() => {
99+
assert_equals(editingHost.outerHTML, `<div contenteditable="plaintext-only">${data.expected}</div>`);
100+
}, `${t.name}: innerHTML`);
101+
}, data.desc);
102+
}
103+
}, "The result should depend on the outermost editing host in the innermost non-editable element whether pasting with or without format");
104+
}, {once: true});
105+
</script>
106+
</head>
107+
<body></body>
108+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="timeout" content="long">
6+
<title>Delete editor in a shadow</title>
7+
<script src="/resources/testharness.js"></script>
8+
<script src="/resources/testharnessreport.js"></script>
9+
<script src="/resources/testdriver.js"></script>
10+
<script src="/resources/testdriver-vendor.js"></script>
11+
<script src="/resources/testdriver-actions.js"></script>
12+
<script src="../include/editor-test-utils.js"></script>
13+
<script>
14+
"use strict";
15+
16+
document.designMode = "on";
17+
18+
addEventListener("load", () => {
19+
const div = document.querySelector("div[contenteditable=plaintext-only]");
20+
const utils = new EditorTestUtils(div);
21+
test(() => {
22+
utils.setupEditingHost("[ABC]");
23+
document.execCommand("bold");
24+
assert_equals(div.outerHTML, `<div contenteditable="plaintext-only"><b>ABC</b></div>`);
25+
}, "contenteditable=plaintext-only in the design mode should not prevent the style edit");
26+
27+
test(() => {
28+
utils.setupEditingHost("A[B]C");
29+
document.execCommand("insertHTML", false, "<b>b</b>");
30+
assert_equals(div.outerHTML, `<div contenteditable="plaintext-only">A<b>b</b>C</div>`);
31+
}, "contenteditable=plaintext-only in the design mode should not suppress style of inserting HTML");
32+
33+
promise_test(async () => {
34+
utils.setupEditingHost("<p>A[]B</p>");
35+
await utils.sendEnterKey();
36+
assert_equals(div.outerHTML, `<div contenteditable="plaintext-only"><p>A</p><p>B</p></div>`);
37+
}, "contenteditable=plaintext-only in the design mode should not cause inserting line break at typing Enter");
38+
}, {once: true});
39+
</script>
40+
</head>
41+
<body>
42+
<div contenteditable="plaintext-only"></div>
43+
</body>
44+
</html>

0 commit comments

Comments
 (0)