diff options
Diffstat (limited to '')
-rw-r--r-- | layout/generic/test/test_bug1623764.html | 292 |
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, " ").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> |