modify-around-inline-element-boundary.tentative.html (10339B)
1 <!doctype html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title>Test Selection.modify("move", "left" or "right", "character") around inline element boundaries</title> 6 <script src="/resources/testharness.js"></script> 7 <script src="/resources/testharnessreport.js"></script> 8 <script src="../../editing/include/editor-test-utils.js"></script> 9 <script> 10 "use strict"; 11 12 addEventListener("load", () => { 13 const testContainer = document.getElementById("testBody"); 14 function stringifySelectionRanges() { 15 let result = "["; 16 for (let i = 0; i < getSelection().rangeCount; i++) { 17 const range = getSelection().getRangeAt(i); 18 if (result != "[") { 19 result += ", " 20 } 21 result += EditorTestUtils.getRangeDescription(range); 22 } 23 return result + "]"; 24 } 25 function stringifyExpectedRange(expectedRange) { 26 return `[${EditorTestUtils.getRangeDescription({ 27 startContainer: expectedRange.container, 28 startOffset: expectedRange.offset, 29 endContainer: expectedRange.container, 30 endOffset: expectedRange.offset, 31 })}]`; 32 } 33 for (const data of [ 34 // Basic tests. If moving caret from middle of a Text, shouldn't go out different Text. 35 // This allows users preserve style at previous cursor position for the new text. 36 { 37 innerHTML: "ab[]c<span>def</span>", 38 direction: "right", 39 expectedResult: function () { 40 return { container: testContainer.firstChild, offset: "abc".length }; 41 }, 42 }, 43 { 44 innerHTML: "abc[]<span>def</span>", 45 direction: "right", 46 expectedResult: function () { 47 return { container: testContainer.querySelector("span").firstChild, offset: "d".length }; 48 }, 49 }, 50 { 51 innerHTML: "<b>ab[]c</b><i>def</i>", 52 direction: "right", 53 expectedResult: function () { 54 return { container: testContainer.querySelector("b").firstChild, offset: "abc".length }; 55 }, 56 }, 57 { 58 innerHTML: "<b>abc[]</b><i>def</i>", 59 direction: "right", 60 expectedResult: function () { 61 return { container: testContainer.querySelector("i").firstChild, offset: "d".length }; 62 }, 63 }, 64 { 65 innerHTML: "abc<span>d[]ef</span>", 66 direction: "left", 67 expectedResult: function () { 68 return { container: testContainer.querySelector("span").firstChild, offset: 0 }; 69 }, 70 }, 71 { 72 innerHTML: "abc<span>[]def</span>", 73 direction: "left", 74 expectedResult: function () { 75 return { container: testContainer.firstChild, offset: "ab".length }; 76 }, 77 }, 78 { 79 innerHTML: "<b>abc</b><i>d[]ef</i>", 80 direction: "left", 81 expectedResult: function () { 82 return { container: testContainer.querySelector("i").firstChild, offset: 0 }; 83 }, 84 }, 85 { 86 innerHTML: "<b>abc</b><i>[]def</i>", 87 direction: "left", 88 expectedResult: function () { 89 return { container: testContainer.querySelector("b").firstChild, offset: "ab".length }; 90 }, 91 }, 92 // Don't skip visible white-space around the inline element boundaries. 93 { 94 innerHTML: "abc[] <span>def</span>", 95 direction: "right", 96 expectedResult: function () { 97 return { container: testContainer.firstChild, offset: "abc ".length }; 98 }, 99 }, 100 { 101 innerHTML: "abc[]<span> def</span>", 102 direction: "right", 103 expectedResult: function () { 104 return { container: testContainer.querySelector("span").firstChild, offset: " ".length }; 105 }, 106 }, 107 { 108 innerHTML: "abc <span>[]def</span>", 109 direction: "left", 110 expectedResult: function () { 111 return { container: testContainer.firstChild, offset: "abc".length }; 112 }, 113 }, 114 { 115 innerHTML: "abc<span> []def</span>", 116 direction: "left", 117 expectedResult: function () { 118 return { container: testContainer.querySelector("span").firstChild, offset: 0 }; 119 }, 120 }, 121 122 // Skip invisible white-spaces around the inline element boundaries. 123 124 // Only the first white-space should be visible when multiple spaces are collapsed. 125 // Therefore, the following tests expect that selection is collapsed after the first white-space. 126 { 127 innerHTML: "abc[] <span>def</span>", 128 direction: "right", 129 expectedResult: function () { 130 return { container: testContainer.firstChild, offset: "abc ".length }; 131 }, 132 }, 133 { 134 innerHTML: "abc[] <span> def</span>", 135 direction: "right", 136 expectedResult: function () { 137 return { container: testContainer.firstChild, offset: "abc ".length }; 138 }, 139 }, 140 { 141 innerHTML: "abc[]<span> def</span>", 142 direction: "right", 143 expectedResult: function () { 144 return { container: testContainer.querySelector("span").firstChild, offset: " ".length }; 145 }, 146 }, 147 { 148 innerHTML: "<span>abc[] </span>def", 149 direction: "right", 150 expectedResult: function () { 151 return { container: testContainer.querySelector("span").firstChild, offset: "abc ".length }; 152 }, 153 }, 154 { 155 innerHTML: "<span>abc[] </span> def", 156 direction: "right", 157 expectedResult: function () { 158 return { container: testContainer.querySelector("span").firstChild, offset: "abc ".length }; 159 }, 160 }, 161 { 162 innerHTML: "<span>abc[]</span> def", 163 direction: "right", 164 expectedResult: function () { 165 return { container: testContainer.lastChild, offset: " ".length }; 166 }, 167 }, 168 // Similarly, these tests expect that selection is collapsed before the first white-space. 169 { 170 innerHTML: "abc <span>[]def</span>", 171 direction: "left", 172 expectedResult: function () { 173 return { container: testContainer.firstChild, offset: "abc".length }; 174 }, 175 }, 176 { 177 innerHTML: "abc <span> []def</span>", 178 direction: "left", 179 expectedResult: function () { 180 return { container: testContainer.firstChild, offset: "abc".length }; 181 }, 182 }, 183 { 184 innerHTML: "abc<span> []def</span>", 185 direction: "left", 186 expectedResult: function () { 187 return { container: testContainer.querySelector("span").firstChild, offset: 0 }; 188 }, 189 }, 190 { 191 innerHTML: "<span>abc </span>[]def", 192 direction: "left", 193 expectedResult: function () { 194 return { container: testContainer.querySelector("span").firstChild, offset: "abc".length }; 195 }, 196 }, 197 { 198 innerHTML: "<span>abc </span> []def", 199 direction: "left", 200 expectedResult: function () { 201 return { container: testContainer.querySelector("span").firstChild, offset: "abc".length }; 202 }, 203 }, 204 { 205 innerHTML: "<span>abc</span> []def", 206 direction: "left", 207 expectedResult: function () { 208 return { container: testContainer.lastChild, offset: 0 }; 209 }, 210 }, 211 // Invisible white-spaces must be skipped by modify(). 212 { 213 innerHTML: "abc[] <span>def</span>", 214 direction: "right", 215 repeat: 2, 216 expectedResult: function () { 217 return { container: testContainer.querySelector("span").firstChild, offset: "d".length }; 218 }, 219 }, 220 { 221 innerHTML: "abc[] <span> def</span>", 222 direction: "right", 223 repeat: 2, 224 expectedResult: function () { 225 return { container: testContainer.querySelector("span").firstChild, offset: " d".length }; 226 }, 227 }, 228 { 229 innerHTML: "abc[]<span> def</span>", 230 direction: "right", 231 repeat: 2, 232 expectedResult: function () { 233 return { container: testContainer.querySelector("span").firstChild, offset: " d".length }; 234 }, 235 }, 236 { 237 innerHTML: "<span>abc[] </span>def", 238 direction: "right", 239 repeat: 2, 240 expectedResult: function () { 241 return { container: testContainer.lastChild, offset: "d".length }; 242 }, 243 }, 244 { 245 innerHTML: "<span>abc[] </span> def", 246 direction: "right", 247 repeat: 2, 248 expectedResult: function () { 249 return { container: testContainer.lastChild, offset: " d".length }; 250 }, 251 }, 252 { 253 innerHTML: "<span>abc[]</span> def", 254 direction: "right", 255 repeat: 2, 256 expectedResult: function () { 257 return { container: testContainer.lastChild, offset: " d".length }; 258 }, 259 }, 260 { 261 innerHTML: "abc <span>[]def</span>", 262 direction: "left", 263 repeat: 2, 264 expectedResult: function () { 265 return { container: testContainer.firstChild, offset: "ab".length }; 266 }, 267 }, 268 { 269 innerHTML: "abc <span> []def</span>", 270 direction: "left", 271 repeat: 2, 272 expectedResult: function () { 273 return { container: testContainer.firstChild, offset: "ab".length }; 274 }, 275 }, 276 { 277 innerHTML: "abc<span> []def</span>", 278 direction: "left", 279 repeat: 2, 280 expectedResult: function () { 281 return { container: testContainer.firstChild, offset: "ab".length }; 282 }, 283 }, 284 { 285 innerHTML: "<span>abc </span>[]def", 286 direction: "left", 287 repeat: 2, 288 expectedResult: function () { 289 return { container: testContainer.querySelector("span").firstChild, offset: "ab".length }; 290 }, 291 }, 292 { 293 innerHTML: "<span>abc </span> []def", 294 direction: "left", 295 repeat: 2, 296 expectedResult: function () { 297 return { container: testContainer.querySelector("span").firstChild, offset: "ab".length }; 298 }, 299 }, 300 { 301 innerHTML: "<span>abc</span> []def", 302 direction: "left", 303 repeat: 2, 304 expectedResult: function () { 305 return { container: testContainer.querySelector("span").firstChild, offset: "ab".length }; 306 }, 307 }, 308 ]) { 309 test(() => { 310 const utils = new EditorTestUtils(testContainer); 311 utils.setupEditingHost(data.innerHTML); 312 for (let i = 0; i < (data.repeat ?? 1); i++) { 313 getSelection().modify("move", data.direction, "character"); 314 } 315 assert_equals( 316 stringifySelectionRanges(), 317 stringifyExpectedRange(data.expectedResult()) 318 ); 319 }, `Selection.modify("move", "${data.direction}", "character")${ 320 data.repeat > 1 ? ` (${data.repeat} times)` : "" 321 } when "${data.innerHTML}"`); 322 } 323 }, {once: true}); 324 </script> 325 </head> 326 <body> 327 <div id="testBody" contenteditable></div> 328 </body> 329 </html>