tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_bug1623764.html (8303B)


      1 <!DOCTYPE html>
      2 <title>Test Windows conventional caret movement behavior</title>
      3 <script src="/tests/SimpleTest/SimpleTest.js"></script>
      4 <script src="/tests/SimpleTest/EventUtils.js"></script>
      5 <link rel="stylesheet" href="/tests/SimpleTest/test.css" />
      6 <style>
      7  [contenteditable] {
      8    font-size: 14px;
      9    font-family: monospace;
     10    overflow-wrap: normal;
     11    outline: none;
     12    width: 35ch;
     13  }
     14  textarea {
     15    font-size: 14px;
     16    font-family: monospace;
     17  }
     18 
     19  .break-word {
     20    overflow-wrap: break-word;
     21  }
     22 </style>
     23 <div id="testDiv" contenteditable></div>
     24 <textarea id="testTextarea" cols=35></textarea>
     25 
     26 <script>
     27 // Call testEach(cases[i]) on console when debugging
     28 /**
     29 * @type {TestCase[]}
     30 *
     31 * @typedef {object} TestCase
     32 * @property {string} title
     33 * @property {string} text Text on which the test runs.
     34 *   The newlines and spaces will be auto-converted if needed.
     35 * @property {boolean} [reverse] Test in both forward and backward directions
     36 * @property {boolean} [backward] Move backward by cmd_wordPrevious
     37 * @property {ElementSpecific} [div] Can be omitted when there is a bug
     38 * @property {ElementSpecific} textarea
     39 *
     40 * @typedef {object} ElementSpecific
     41 * @property {number} expectedOffset Expected offset after a caret move
     42 * @property {number} [expectedNodeIndex] The index of child node with focus after a caret move.
     43 *   -1 means the parent node.
     44 * @property {number} [initialOffset=0] initialOffset offset before a caret move
     45 * @property {number} [initialNodeIndex]
     46 */
     47 
     48 const kFirstWordLength = "Supercalifragilisticexpialidocious".length; // 34
     49 const kParentNodeIndex = -1; // special value
     50 const cases = [
     51  {
     52    title: "Eats inline whitespaces after word",
     53    text: "Supercalifragilisticexpialidocious foo bar fussball",
     54    reverse: true,
     55    div: {
     56      expectedOffset: kFirstWordLength + 1
     57    },
     58    textarea: {
     59      expectedOffset: kFirstWordLength + 1
     60    }
     61  },
     62  {
     63    title: "Eats inline whitespaces across a wrapped line after word",
     64    text: "Supercalifragilisticexpialidocious       foo bar fussball",
     65    reverse: true,
     66    div: {
     67      expectedOffset: kFirstWordLength + 7
     68    },
     69    textarea: {
     70      expectedOffset: kFirstWordLength + 7
     71    }
     72  },
     73  {
     74    title: "Eats inline whitespaces across multiple wrapped lines after word",
     75    text: `Supercalifragilisticexpialidocious                                                                 foo bar fussball`,
     76    reverse: true,
     77    div: {
     78      expectedOffset: kFirstWordLength + 65
     79    },
     80    textarea: {
     81      expectedOffset: kFirstWordLength + 65
     82    }
     83  },
     84  {
     85    title: "Eats inline whitespaces after a whole word and stop before a newline",
     86    text: "Supercalifragilisticexpialidocious \nfoo bar fussball",
     87    reverse: true,
     88    div: {
     89      expectedOffset: 1,
     90      expectedNodeIndex: kParentNodeIndex
     91    },
     92    textarea: {
     93      expectedOffset: kFirstWordLength + 1
     94    }
     95  },
     96  {
     97    title: "Eats inline whitespaces after a partial word and stop before a newline",
     98    text: "Supercalifragilisticexpialidocious \nfoo bar fussball",
     99    div: {
    100      initialOffset: 5,
    101      expectedOffset: 1,
    102      expectedNodeIndex: kParentNodeIndex
    103    },
    104    textarea: {
    105      initialOffset: 5,
    106      expectedOffset: kFirstWordLength + 1
    107    }
    108  },
    109  {
    110    title: "Eats inline whitespaces without a word and stop before a newline",
    111    text: "Supercalifragilisticexpialidocious \n      foo bar fussball",
    112    // TODO(krosylight): Currently ClusterIterator internally skips trailing whitespace
    113    // div: {
    114    //   initialOffset: kFirstWordLength,
    115    //   expectedOffset: 1,
    116    //   expectedNodeIndex: kParentNodeIndex
    117    // },
    118    textarea: {
    119      initialOffset: kFirstWordLength,
    120      expectedOffset: kFirstWordLength + 1
    121    }
    122  },
    123  {
    124    title: "Jumps to the next line and eats inline whitespaces",
    125    text: "Supercalifragilisticexpialidocious \n      foo bar fussball",
    126    reverse: true,
    127    div: {
    128      initialOffset: kFirstWordLength + 1,
    129      expectedOffset: 6,
    130      expectedNodeIndex: 2
    131    },
    132    textarea: {
    133      initialOffset: kFirstWordLength + 1,
    134      expectedOffset: kFirstWordLength + 8
    135    }
    136  },
    137  {
    138    title: "Stops on whitespaces after word",
    139    text: "Supercalifragilisticexpialidocious \n      foo bar fussball",
    140    backward: true,
    141    div: {
    142      initialOffset: 8,
    143      initialNodeIndex: 2,
    144      expectedOffset: 6,
    145      expectedNodeIndex: 2
    146    },
    147    textarea: {
    148      initialOffset: 44, // middle of "foo"
    149      expectedOffset: 42
    150    }
    151  },
    152  // TODO(krosylight): Currently no way to tell a word break is from line wrapping
    153  // {
    154  //   title: "Ignore a word break introduced by line wrapping",
    155  //   text: "Supercalifragilisticexpialidociouspostfix",
    156  //   className: "narrow break-word",
    157  //   div: {
    158  //     expectedOffset: kFirstWordLength + 7
    159  //   },
    160  //   textarea: {
    161  //     expectedOffset: kFirstWordLength + 7
    162  //   }
    163  // }
    164  {
    165    title: "Jump only one line at an empty line end",
    166    text: "Supercalifragilisticexpialidocious \n\nfoo bar fussball",
    167    reverse: true,
    168    div: {
    169      initialOffset: 2,
    170      initialNodeIndex: kParentNodeIndex,
    171      expectedOffset: 0,
    172      expectedNodeIndex: 3
    173    },
    174    textarea: {
    175      initialOffset: kFirstWordLength + 2,
    176      expectedOffset: kFirstWordLength + 3
    177    }
    178  },
    179  {
    180    title: "Jump only one line at a non-empty line end",
    181    text: "Supercalifragilisticexpialidocious \n\nfoo bar fussball",
    182    reverse: true,
    183    div: {
    184      initialOffset: kFirstWordLength + 1,
    185      expectedOffset: 2,
    186      expectedNodeIndex: -1
    187    },
    188    textarea: {
    189      initialOffset: kFirstWordLength + 1,
    190      expectedOffset: kFirstWordLength + 2
    191    }
    192  },
    193 ];
    194 
    195 SimpleTest.waitForExplicitFinish();
    196 SimpleTest.waitForFocus(async () => {
    197  await SpecialPowers.pushPrefEnv({
    198    set: [["layout.word_select.eat_space_to_next_word", true]]
    199  });
    200  // Test on div first to prevent Bug 1623413
    201  for (const testCase of cases) {
    202    if (testCase.div) {
    203      testOnDiv(testCase);
    204    }
    205  }
    206  for (const testCase of cases) {
    207    testOnTextarea(testCase);
    208  }
    209  SimpleTest.finish();
    210 });
    211 
    212 /** @param {TestCase} testCase */
    213 function testOnDiv(testCase) {
    214  const {
    215    title,
    216    backward = false,
    217    reverse = false,
    218  } = testCase;
    219  const {
    220    initialOffset = 0,
    221    expectedOffset,
    222  } = testCase.div;
    223  if (expectedOffset === undefined) {
    224    throw new Error("`expectedOffset` must be defined.")
    225  }
    226 
    227  testDiv.innerHTML = testCase.text.replaceAll(/ {2}/g, " &nbsp;").replaceAll(/\n/g, "<br>");
    228  const initialNode = childNode(testDiv, testCase.div.initialNodeIndex);
    229  const expectedNode = childNode(testDiv, testCase.div.expectedNodeIndex);
    230 
    231  const selection = getSelection();
    232  selection.collapse(initialNode, initialOffset);
    233 
    234  moveByWord(backward);
    235 
    236  is(selection.focusNode, expectedNode, `focusNode in "${title}"`);
    237  is(selection.focusOffset, expectedOffset, `focusOffset in "${title}"`);
    238 
    239  if (reverse) {
    240    selection.collapse(expectedNode, expectedOffset);
    241 
    242    moveByWord(!backward);
    243 
    244    is(selection.focusNode, initialNode, `focusNode with reversed selection in "${title}"`);
    245    is(selection.focusOffset, initialOffset, `focusOffset with reversed selection in "${title}"`);
    246  }
    247 }
    248 
    249 function testOnTextarea(testCase) {
    250  const {
    251    title,
    252    backward = false,
    253    reverse = false,
    254  } = testCase;
    255  const {
    256    initialOffset = 0,
    257    expectedOffset,
    258  } = testCase.textarea;
    259  if (expectedOffset === undefined) {
    260    throw new Error("`expectedOffset` must be defined.")
    261  }
    262 
    263  testTextarea.value = testCase.text;
    264  testTextarea.selectionStart = testTextarea.selectionEnd = initialOffset;
    265  testTextarea.focus();
    266 
    267  moveByWord(backward);
    268 
    269  is(testTextarea.selectionStart, expectedOffset, `selectionStart in "${title}"`);
    270 
    271  if (reverse) {
    272    testTextarea.selectionStart = testTextarea.selectionEnd = expectedOffset;
    273 
    274    moveByWord(!backward);
    275 
    276    is(testTextarea.selectionStart, initialOffset, `selectionStart with reversed selection in "${title}"`);
    277  }
    278 }
    279 
    280 function childNode(parent, index = 0) {
    281  if (index === kParentNodeIndex) {
    282    return parent;
    283  }
    284  return parent.childNodes[index];
    285 }
    286 
    287 /** @param {boolean} backward */
    288 function moveByWord(backward) {
    289  const dir = backward ? "Previous" : "Next";
    290  SpecialPowers.doCommand(window, `cmd_word${dir}`);
    291 }
    292 </script>