summaryrefslogtreecommitdiffstats
path: root/layout/generic/test/test_bug1623764.html
diff options
context:
space:
mode:
Diffstat (limited to 'layout/generic/test/test_bug1623764.html')
-rw-r--r--layout/generic/test/test_bug1623764.html292
1 files changed, 292 insertions, 0 deletions
diff --git a/layout/generic/test/test_bug1623764.html b/layout/generic/test/test_bug1623764.html
new file mode 100644
index 0000000000..eba1bbf85f
--- /dev/null
+++ b/layout/generic/test/test_bug1623764.html
@@ -0,0 +1,292 @@
+<!DOCTYPE html>
+<title>Test Windows conventional caret movement behavior</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
+<style>
+ [contenteditable] {
+ font-size: 14px;
+ font-family: monospace;
+ overflow-wrap: normal;
+ outline: none;
+ width: 35ch;
+ }
+ textarea {
+ font-size: 14px;
+ font-family: monospace;
+ }
+
+ .break-word {
+ overflow-wrap: break-word;
+ }
+</style>
+<div id="testDiv" contenteditable></div>
+<textarea id="testTextarea" cols=35></textarea>
+
+<script>
+// Call testEach(cases[i]) on console when debugging
+/**
+ * @type {TestCase[]}
+ *
+ * @typedef {object} TestCase
+ * @property {string} title
+ * @property {string} text Text on which the test runs.
+ * The newlines and spaces will be auto-converted if needed.
+ * @property {boolean} [reverse] Test in both forward and backward directions
+ * @property {boolean} [backward] Move backward by cmd_wordPrevious
+ * @property {ElementSpecific} [div] Can be omitted when there is a bug
+ * @property {ElementSpecific} textarea
+ *
+ * @typedef {object} ElementSpecific
+ * @property {number} expectedOffset Expected offset after a caret move
+ * @property {number} [expectedNodeIndex] The index of child node with focus after a caret move.
+ * -1 means the parent node.
+ * @property {number} [initialOffset=0] initialOffset offset before a caret move
+ * @property {number} [initialNodeIndex]
+ */
+
+const kFirstWordLength = "Supercalifragilisticexpialidocious".length; // 34
+const kParentNodeIndex = -1; // special value
+const cases = [
+ {
+ title: "Eats inline whitespaces after word",
+ text: "Supercalifragilisticexpialidocious foo bar fussball",
+ reverse: true,
+ div: {
+ expectedOffset: kFirstWordLength + 1
+ },
+ textarea: {
+ expectedOffset: kFirstWordLength + 1
+ }
+ },
+ {
+ title: "Eats inline whitespaces across a wrapped line after word",
+ text: "Supercalifragilisticexpialidocious foo bar fussball",
+ reverse: true,
+ div: {
+ expectedOffset: kFirstWordLength + 7
+ },
+ textarea: {
+ expectedOffset: kFirstWordLength + 7
+ }
+ },
+ {
+ title: "Eats inline whitespaces across multiple wrapped lines after word",
+ text: `Supercalifragilisticexpialidocious foo bar fussball`,
+ reverse: true,
+ div: {
+ expectedOffset: kFirstWordLength + 65
+ },
+ textarea: {
+ expectedOffset: kFirstWordLength + 65
+ }
+ },
+ {
+ title: "Eats inline whitespaces after a whole word and stop before a newline",
+ text: "Supercalifragilisticexpialidocious \nfoo bar fussball",
+ reverse: true,
+ div: {
+ expectedOffset: 1,
+ expectedNodeIndex: kParentNodeIndex
+ },
+ textarea: {
+ expectedOffset: kFirstWordLength + 1
+ }
+ },
+ {
+ title: "Eats inline whitespaces after a partial word and stop before a newline",
+ text: "Supercalifragilisticexpialidocious \nfoo bar fussball",
+ div: {
+ initialOffset: 5,
+ expectedOffset: 1,
+ expectedNodeIndex: kParentNodeIndex
+ },
+ textarea: {
+ initialOffset: 5,
+ expectedOffset: kFirstWordLength + 1
+ }
+ },
+ {
+ title: "Eats inline whitespaces without a word and stop before a newline",
+ text: "Supercalifragilisticexpialidocious \n foo bar fussball",
+ // TODO(krosylight): Currently ClusterIterator internally skips trailing whitespace
+ // div: {
+ // initialOffset: kFirstWordLength,
+ // expectedOffset: 1,
+ // expectedNodeIndex: kParentNodeIndex
+ // },
+ textarea: {
+ initialOffset: kFirstWordLength,
+ expectedOffset: kFirstWordLength + 1
+ }
+ },
+ {
+ title: "Jumps to the next line and eats inline whitespaces",
+ text: "Supercalifragilisticexpialidocious \n foo bar fussball",
+ reverse: true,
+ div: {
+ initialOffset: kFirstWordLength + 1,
+ expectedOffset: 6,
+ expectedNodeIndex: 2
+ },
+ textarea: {
+ initialOffset: kFirstWordLength + 1,
+ expectedOffset: kFirstWordLength + 8
+ }
+ },
+ {
+ title: "Stops on whitespaces after word",
+ text: "Supercalifragilisticexpialidocious \n foo bar fussball",
+ backward: true,
+ div: {
+ initialOffset: 8,
+ initialNodeIndex: 2,
+ expectedOffset: 6,
+ expectedNodeIndex: 2
+ },
+ textarea: {
+ initialOffset: 44, // middle of "foo"
+ expectedOffset: 42
+ }
+ },
+ // TODO(krosylight): Currently no way to tell a word break is from line wrapping
+ // {
+ // title: "Ignore a word break introduced by line wrapping",
+ // text: "Supercalifragilisticexpialidociouspostfix",
+ // className: "narrow break-word",
+ // div: {
+ // expectedOffset: kFirstWordLength + 7
+ // },
+ // textarea: {
+ // expectedOffset: kFirstWordLength + 7
+ // }
+ // }
+ {
+ title: "Jump only one line at an empty line end",
+ text: "Supercalifragilisticexpialidocious \n\nfoo bar fussball",
+ reverse: true,
+ div: {
+ initialOffset: 2,
+ initialNodeIndex: kParentNodeIndex,
+ expectedOffset: 0,
+ expectedNodeIndex: 3
+ },
+ textarea: {
+ initialOffset: kFirstWordLength + 2,
+ expectedOffset: kFirstWordLength + 3
+ }
+ },
+ {
+ title: "Jump only one line at a non-empty line end",
+ text: "Supercalifragilisticexpialidocious \n\nfoo bar fussball",
+ reverse: true,
+ div: {
+ initialOffset: kFirstWordLength + 1,
+ expectedOffset: 2,
+ expectedNodeIndex: -1
+ },
+ textarea: {
+ initialOffset: kFirstWordLength + 1,
+ expectedOffset: kFirstWordLength + 2
+ }
+ },
+];
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [["layout.word_select.eat_space_to_next_word", true]]
+ });
+ // Test on div first to prevent Bug 1623413
+ for (const testCase of cases) {
+ if (testCase.div) {
+ testOnDiv(testCase);
+ }
+ }
+ for (const testCase of cases) {
+ testOnTextarea(testCase);
+ }
+ SimpleTest.finish();
+});
+
+/** @param {TestCase} testCase */
+function testOnDiv(testCase) {
+ const {
+ title,
+ backward = false,
+ reverse = false,
+ } = testCase;
+ const {
+ initialOffset = 0,
+ expectedOffset,
+ } = testCase.div;
+ if (expectedOffset === undefined) {
+ throw new Error("`expectedOffset` must be defined.")
+ }
+
+ testDiv.innerHTML = testCase.text.replaceAll(/ {2}/g, " &nbsp;").replaceAll(/\n/g, "<br>");
+ const initialNode = childNode(testDiv, testCase.div.initialNodeIndex);
+ const expectedNode = childNode(testDiv, testCase.div.expectedNodeIndex);
+
+ const selection = getSelection();
+ selection.collapse(initialNode, initialOffset);
+
+ moveByWord(backward);
+
+ is(selection.focusNode, expectedNode, `focusNode in "${title}"`);
+ is(selection.focusOffset, expectedOffset, `focusOffset in "${title}"`);
+
+ if (reverse) {
+ selection.collapse(expectedNode, expectedOffset);
+
+ moveByWord(!backward);
+
+ is(selection.focusNode, initialNode, `focusNode with reversed selection in "${title}"`);
+ is(selection.focusOffset, initialOffset, `focusOffset with reversed selection in "${title}"`);
+ }
+}
+
+function testOnTextarea(testCase) {
+ const {
+ title,
+ backward = false,
+ reverse = false,
+ } = testCase;
+ const {
+ initialOffset = 0,
+ expectedOffset,
+ } = testCase.textarea;
+ if (expectedOffset === undefined) {
+ throw new Error("`expectedOffset` must be defined.")
+ }
+
+ testTextarea.value = testCase.text;
+ testTextarea.selectionStart = testTextarea.selectionEnd = initialOffset;
+ testTextarea.focus();
+
+ moveByWord(backward);
+
+ is(testTextarea.selectionStart, expectedOffset, `selectionStart in "${title}"`);
+
+ if (reverse) {
+ testTextarea.selectionStart = testTextarea.selectionEnd = expectedOffset;
+
+ moveByWord(!backward);
+
+ is(testTextarea.selectionStart, initialOffset, `selectionStart with reversed selection in "${title}"`);
+ }
+}
+
+function childNode(parent, index = 0) {
+ if (index === kParentNodeIndex) {
+ return parent;
+ }
+ return parent.childNodes[index];
+}
+
+/** @param {boolean} backward */
+function moveByWord(backward) {
+ const dir = backward ? "Previous" : "Next";
+ SpecialPowers.doCommand(window, `cmd_word${dir}`);
+}
+</script>