278 lines
10 KiB
HTML
278 lines
10 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="timeout" content="long">
|
|
<title>Test for Bug 1783641</title>
|
|
<script src=/resources/testharness.js></script>
|
|
<script src=/resources/testharnessreport.js></script>
|
|
<script src="/resources/testdriver.js"></script>
|
|
<script src="/resources/testdriver-actions.js"></script>
|
|
<style>
|
|
.testStyle {
|
|
font-family: 'Courier New', Courier, monospace;
|
|
font-size: 12px;
|
|
padding: 0px;
|
|
width: 200px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1783641">Mozilla Bug 1783641</a><br />
|
|
<span class="testStyle" id="placeholder"></span>
|
|
<input class="testStyle" type="text" />
|
|
<div class="testStyle" contenteditable></div>
|
|
<textarea class="testStyle"></textarea>
|
|
<script>
|
|
|
|
promise_test(async t => {
|
|
await new Promise(resolve => { window.onload = resolve; });
|
|
|
|
await SpecialPowers.pushPrefEnv({
|
|
set: [
|
|
["editor.word_select.delete_space_after_doubleclick_selection", true],
|
|
["layout.word_select.eat_space_to_next_word", false]
|
|
]
|
|
});
|
|
}, "Test setup");
|
|
const placeHolder = document.getElementById("placeholder");
|
|
const deleteKey = "\uE017";
|
|
|
|
function waitForRender() {
|
|
return new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
|
};
|
|
|
|
for (const selector of ["input", "div[contenteditable]", "textarea"]) {
|
|
const editableElement = document.querySelector(selector);
|
|
editableElement.focus();
|
|
|
|
/**
|
|
* Helper functions to set or get the value and the current selection of `editableElement`,
|
|
* regardless of its actual type.
|
|
*/
|
|
const setValue = (aValue) => {
|
|
editableElement.tagName.toLowerCase() == "div"
|
|
? editableElement.innerHTML = aValue
|
|
: editableElement.value = aValue;
|
|
}
|
|
const getValue = () => {
|
|
return editableElement.tagName.toLowerCase() == "div"
|
|
? editableElement.innerHTML
|
|
: editableElement.value;
|
|
};
|
|
const getSelection = () => {
|
|
return editableElement.tagName.toLowerCase() == "div"
|
|
? document.getSelection().toString()
|
|
: editableElement.value.substring(
|
|
editableElement.selectionStart,
|
|
editableElement.selectionEnd
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Places a double click in `editableElement` at exactly the end of `aPlaceHolderText` and press delete.
|
|
* `aPlaceholderText` therefore should contain the same text as the value of the `editableElement`
|
|
* up to the point where the doubleclick should happen.
|
|
*
|
|
* If `aSelectionValue` is defined, the selection created by the double click is compared to `aSelectionValue`.
|
|
*/
|
|
const doubleClickAndDelete = async (aPlaceHolderText, aSelectionValue = undefined) => {
|
|
placeHolder.innerHTML = aPlaceHolderText;
|
|
editableElement.focus();
|
|
await waitForRender();
|
|
const absInputPos = editableElement.getBoundingClientRect();
|
|
selectionOffset = {
|
|
x: placeHolder.getBoundingClientRect().width,
|
|
y: Math.floor(placeHolder.getBoundingClientRect().height / 2)
|
|
};
|
|
await (new test_driver.Actions()
|
|
// for some reason this still doesn't work:
|
|
// .pointerMove(Math.floor(selectionOffset.x), Math.floor(selectionOffset.y), { origin: editableElement })
|
|
// but this does:
|
|
.pointerMove(
|
|
Math.floor(absInputPos.x + selectionOffset.x),
|
|
Math.floor(absInputPos.y + selectionOffset.y),
|
|
{ origin: "viewport" }
|
|
)
|
|
.pointerDown()
|
|
.pointerUp()
|
|
.pointerDown()
|
|
.pointerUp())
|
|
.send()
|
|
await waitForRender();
|
|
if (aSelectionValue !== undefined) {
|
|
assert_equals(getSelection(), aSelectionValue, "Wrong selection value!");
|
|
}
|
|
return test_driver.send_keys(editableElement, deleteKey);
|
|
};
|
|
if (editableElement.tagName.toLowerCase() == "div") {
|
|
promise_test(async t => {
|
|
setValue("<p>abc def<span></span></p>");
|
|
await doubleClickAndDelete("abc de", "def");
|
|
await waitForRender();
|
|
assert_equals(
|
|
getValue(),
|
|
"<p>abc</p>",
|
|
"The <span> at the end of the string must be removed, as well as the whitespace in between words.");
|
|
}, `${editableElement.tagName}: An empty span at the end of the selection should be considered end of selection!`);
|
|
}
|
|
promise_test(async t => {
|
|
setValue("one two");
|
|
await doubleClickAndDelete("on", "one");
|
|
await waitForRender();
|
|
assert_equals(
|
|
getValue(),
|
|
"two",
|
|
"The whitespace between words must be removed when a word at the beginning is selected and deleted!"
|
|
);
|
|
}, `${editableElement.tagName}: Remove word at the beginning of string should remove the whitespace in between.`);
|
|
|
|
promise_test(async t => {
|
|
setValue("one two");
|
|
await doubleClickAndDelete("one tw", "two");
|
|
await waitForRender();
|
|
assert_equals(
|
|
getValue(),
|
|
"one",
|
|
"The whitespace between words must be removed when a word is selected at the end of the string and deleted!"
|
|
);
|
|
}, `${editableElement.tagName}: Remove word at the end of a string should remove the whitespace in between.`);
|
|
|
|
promise_test(async t => {
|
|
setValue("one two three");
|
|
await doubleClickAndDelete("one tw", "two");
|
|
await waitForRender();
|
|
assert_equals(
|
|
getValue(),
|
|
"one three",
|
|
"One whitespace between words must be removed when a word is selected and deleted!"
|
|
);
|
|
await waitForRender();
|
|
if (editableElement.tagName.toLowerCase() == "div") {
|
|
document.getSelection().setBaseAndExtent(
|
|
editableElement.firstChild,
|
|
0,
|
|
editableElement.firstChild,
|
|
3
|
|
);
|
|
}
|
|
else {
|
|
editableElement.setSelectionRange(0, 3);
|
|
}
|
|
await test_driver.send_keys(editableElement, deleteKey);
|
|
// div[contenteditable] returns ' three' here.
|
|
assert_equals(
|
|
getValue().replace(/ /g, " "),
|
|
" three",
|
|
"The whitespace must not be removed when selecting a word without doubleclicking it!"
|
|
);
|
|
|
|
}, `${editableElement.tagName}: Remove word in the middle of a string should remove one whitespace ` +
|
|
"only if selection is created by double click.");
|
|
|
|
promise_test(async t => {
|
|
setValue("one two three");
|
|
await doubleClickAndDelete("one tw", "two");
|
|
await waitForRender();
|
|
assert_equals(
|
|
getValue(),
|
|
"one three",
|
|
"One whitespace between words must be removed when a word is selected and deleted!"
|
|
);
|
|
}, `${editableElement.tagName}: Only one whitespace character should be removed when there are multiple.`);
|
|
|
|
promise_test(async t => {
|
|
setValue("one two");
|
|
await doubleClickAndDelete("one tw", "two");
|
|
await waitForRender();
|
|
assert_equals(
|
|
getValue(),
|
|
"one ",
|
|
"One whitespace character between words must be removed when a word is selected and deleted!"
|
|
);
|
|
}, `${editableElement.tagName}: Only one whitespace character should be removed when ` +
|
|
"there are multiple whitespaces and the deleted range is the end of the string.");
|
|
|
|
promise_test(async t => {
|
|
setValue("one two, three");
|
|
await doubleClickAndDelete("one tw", "two");
|
|
await waitForRender();
|
|
assert_equals(
|
|
getValue(),
|
|
"one, three",
|
|
"The whitespace in front of the selected word must be removed when punctuation follows selection!"
|
|
);
|
|
}, `${editableElement.tagName}: Removing a word before punctuation should remove the whitespace.`);
|
|
|
|
promise_test(async t => {
|
|
setValue("one, two");
|
|
await doubleClickAndDelete("one, tw", "two");
|
|
await waitForRender();
|
|
assert_equals(
|
|
getValue(),
|
|
"one,",
|
|
"The whitespace in front of the selected word must be removed!"
|
|
);
|
|
}, `${editableElement.tagName}: Remove a word after punctuation should remove the whitespace.`);
|
|
|
|
promise_test(async t => {
|
|
setValue("one\u00A0two, three"); // adds a
|
|
await doubleClickAndDelete("one tw", "two");
|
|
await waitForRender();
|
|
assert_equals(
|
|
getValue(),
|
|
"one, three",
|
|
"The whitespace between words must be removed when a word is selected and deleted!"
|
|
);
|
|
}, `${editableElement.tagName}: Removing a word between a and punctuation should remove the nbsp character.`);
|
|
|
|
if (editableElement.tagName.toLowerCase() == "div") {
|
|
promise_test(async t => {
|
|
setValue("one two<br>");
|
|
await doubleClickAndDelete("one tw", "two");
|
|
await waitForRender();
|
|
assert_equals(
|
|
getValue(),
|
|
"one",
|
|
"The line break must be preserved!"
|
|
);
|
|
}, `${editableElement.tagName}: Removing a word in front of a line break should preserve the line break.`);
|
|
}
|
|
if (editableElement.tagName.toLowerCase() == "textarea") {
|
|
promise_test(async t => {
|
|
setValue("one two\n");
|
|
await doubleClickAndDelete("one tw", "two");
|
|
await waitForRender();
|
|
assert_equals(
|
|
getValue(),
|
|
"one\n",
|
|
"The line break must be preserved!"
|
|
);
|
|
}, `${editableElement.tagName}: RRemoving a word in front of a line break should preserve the line break.`);
|
|
}
|
|
promise_test(async t => {
|
|
setValue("one two");
|
|
await doubleClickAndDelete("on", "one");
|
|
await waitForRender();
|
|
assert_equals(
|
|
getValue(),
|
|
"two",
|
|
"The whitespace between words must be removed when a word at the beginning is selected and deleted!"
|
|
);
|
|
document.execCommand("undo", false, null);
|
|
assert_equals(
|
|
getValue(),
|
|
"one two",
|
|
"Undo action must restore the original state!"
|
|
);
|
|
document.execCommand("redo", false, null);
|
|
assert_equals(
|
|
getValue(),
|
|
"two",
|
|
"Redo action must remove the word and whitespace again!"
|
|
);
|
|
}, `${editableElement.tagName}: Undo and Redo actions should take the removed whitespace into account.`);
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|