dir-auto-dynamic-changes.window.js (15588B)
1 // META: script=dir-shadow-utils.js 2 3 test(t => { 4 let a = setup_tree(` 5 <div id="a" dir="auto"> 6 <div id="b"></div> 7 hello 8 </div> 9 `); 10 11 let acs = getComputedStyle(a); 12 assert_true(a.matches(":dir(ltr)"), ":dir(ltr) matches before insertion"); 13 assert_false(a.matches(":dir(rtl)"), ":dir(rtl) does not match before insertion"); 14 assert_equals(acs.direction, "ltr", "CSSdirection before insertion"); 15 b.innerHTML = "\u05D0"; 16 assert_false(a.matches(":dir(ltr)"), ":dir(ltr) does not match after insertion"); 17 assert_true(a.matches(":dir(rtl)"), ":dir(rtl) matches after insertion"); 18 assert_equals(acs.direction, "rtl", "CSSdirection after insertion"); 19 20 a.remove(); 21 }, "dynamic insertion of RTL text in a child element"); 22 23 test(() => { 24 let div_rtlchar = document.createElement("div"); 25 div_rtlchar.innerHTML = "\u05D0"; 26 27 let container1 = document.createElement("div"); 28 document.body.appendChild(container1); 29 let container2 = document.createElement("div"); 30 31 for (let container of [container1, container2]) { 32 container.dir = "auto"; 33 assert_true(container.matches(":dir(ltr)")); 34 container.appendChild(div_rtlchar); 35 assert_false(container.matches(":dir(ltr)")); 36 div_rtlchar.remove(); 37 assert_true(container.matches(":dir(ltr)")); 38 } 39 40 container1.remove(); 41 }, "dir=auto changes for content insertion and removal, in and out of document"); 42 43 test(() => { 44 let [tree, shadow] = setup_tree(` 45 <div> 46 <div id="root"> 47 <span id="l">A</span> 48 <span id="r">\u05D0</span> 49 </div> 50 </div> 51 `, ` 52 <slot id="one" name="one" dir="auto">\u05D0</slot> 53 <slot id="two" dir="auto"></slot> 54 `); 55 56 let one = shadow.getElementById("one"); 57 let two = shadow.getElementById("two"); 58 let l = tree.querySelector("#l"); 59 let r = tree.querySelector("#r"); 60 assert_false(one.matches(":dir(ltr)"), "#one while empty"); 61 assert_true(two.matches(":dir(ltr)"), "#two with both spans"); 62 l.slot = "one"; 63 assert_true(one.matches(":dir(ltr)"), "#one with LTR child span"); 64 assert_false(two.matches(":dir(ltr)"), "#two with RTL child span"); 65 r.slot = "one"; 66 assert_true(one.matches(":dir(ltr)"), "#one with both child spans"); 67 assert_true(two.matches(":dir(ltr)"), "#two while empty"); 68 l.slot = ""; 69 assert_false(one.matches(":dir(ltr)"), "#one with RTL child span"); 70 assert_true(two.matches(":dir(ltr)"), "#two with LTR child span"); 71 72 tree.remove(); 73 }, "dir=auto changes for slot reassignment"); 74 75 test(() => { 76 let [tree, shadow] = setup_tree(` 77 <div dir=auto> 78 <div id=root> 79 <div id=text>A</div> 80 </div> 81 </div> 82 `, ` 83 <div dir=ltr> 84 <slot id=slot dir=auto></slot> 85 </div> 86 `); 87 88 let text = tree.querySelector("#text"); 89 let slot = shadow.querySelector("#slot"); 90 91 assert_true(tree.matches(":dir(ltr)"), "node tree ancestor before first text change"); 92 assert_true(slot.matches(":dir(ltr)"), "slot before first text change"); 93 text.innerText = "\u05D0"; 94 assert_false(tree.matches(":dir(ltr)"), "node tree ancestor after first text change"); 95 assert_false(slot.matches(":dir(ltr)"), "slot after first text change"); 96 tree.dir = "rtl"; 97 assert_false(tree.matches(":dir(ltr)"), "node tree ancestor before second text change"); 98 assert_false(slot.matches(":dir(ltr)"), "slot before second text change"); 99 text.innerText = "A"; 100 assert_false(tree.matches(":dir(ltr)"), "node tree ancestor after second text change"); 101 assert_true(slot.matches(":dir(ltr)"), "slot after second text change"); 102 slot.dir = "ltr"; 103 assert_false(tree.matches(":dir(ltr)"), "node tree ancestor before third text change"); 104 assert_true(slot.matches(":dir(ltr)"), "slot before third text change"); 105 text.innerText = "\u05D0"; 106 assert_false(tree.matches(":dir(ltr)"), "node tree ancestor after third text change"); 107 assert_true(slot.matches(":dir(ltr)"), "slot after third text change"); 108 slot.dir = "auto"; 109 tree.dir = "auto"; 110 assert_false(tree.matches(":dir(ltr)"), "node tree ancestor after fourth text change"); 111 assert_false(slot.matches(":dir(ltr)"), "slot after fourth text change"); 112 text.innerText = "A"; 113 assert_true(tree.matches(":dir(ltr)"), "node tree ancestor before fourth text change"); 114 assert_true(slot.matches(":dir(ltr)"), "slot before fourth text change"); 115 slot.dir = "rtl"; 116 assert_true(tree.matches(":dir(ltr)"), "node tree ancestor before fifth text change"); 117 assert_false(slot.matches(":dir(ltr)"), "slot before fifth text change"); 118 text.innerText = "\u05D0"; 119 assert_false(tree.matches(":dir(ltr)"), "node tree ancestor before fifth text change"); 120 assert_false(slot.matches(":dir(ltr)"), "slot before fifth text change"); 121 122 tree.remove(); 123 }, "text changes affecting both slot and ancestor with dir=auto"); 124 125 test(() => { 126 let tree = setup_tree(` 127 <div dir="auto"> 128 <span id="a1">A</span> 129 <span id="aleph1">\u05D0</span> 130 <span id="a2">A</span> 131 <span id="aleph2">\u05D0</span> 132 </div> 133 `); 134 135 let a1 = tree.querySelector("#a1"); 136 let aleph1 = tree.querySelector("#aleph1"); 137 assert_true(tree.matches(":dir(ltr)"), "initial state"); 138 assert_false(tree.matches(":dir(rtl)"), "initial state"); 139 a1.dir = "ltr"; 140 assert_false(tree.matches(":dir(ltr)"), "after change 1"); 141 a1.dir = "invalid"; 142 assert_true(tree.matches(":dir(ltr)"), "after change 2"); 143 a1.dir = "rtl"; 144 assert_false(tree.matches(":dir(ltr)"), "after change 3"); 145 a1.removeAttribute("dir"); 146 assert_true(tree.matches(":dir(ltr)"), "after change 4"); 147 a1.dir = "invalid"; 148 assert_true(tree.matches(":dir(ltr)"), "after change 5"); 149 a1.dir = "rtl"; 150 assert_false(tree.matches(":dir(ltr)"), "after change 6"); 151 aleph1.dir = "auto"; 152 assert_true(tree.matches(":dir(ltr)"), "after change 7"); 153 aleph1.dir = "invalid"; 154 assert_false(tree.matches(":dir(ltr)"), "after change 8"); 155 156 tree.remove(); 157 }, "dynamic changes to subtrees excluded as a result of the dir attribute"); 158 159 test(() => { 160 let tree = setup_tree(` 161 <div dir="auto"> 162 <!-- element goes here --> 163 </div> 164 `); 165 166 let element = document.createElementNS("namespace", "element"); 167 let text = document.createTextNode("\u05D0"); 168 element.appendChild(text); 169 tree.prepend(element); 170 assert_not_equals(element.namespaceURI, tree.namespaceURI); 171 172 assert_true(tree.matches(":dir(rtl)"), "initial state"); 173 assert_false(tree.matches(":dir(ltr)"), "initial state"); 174 text.data = "A"; 175 assert_true(tree.matches(":dir(ltr)"), "after dynamic change"); 176 assert_false(tree.matches(":dir(rtl)"), "after dynamic change"); 177 178 tree.remove(); 179 }, "dynamic changes inside of non-HTML elements"); 180 181 test(() => { 182 let [tree, shadow] = setup_tree(` 183 <div dir="auto"> 184 <div id="root"> 185 <element xmlns="namespace">A</element> 186 \u05D0 187 </div> 188 </div> 189 `, ` 190 <div dir="ltr"> 191 <slot dir="auto">\u05D0</slot> 192 </div> 193 `); 194 195 let element = tree.querySelector("element"); 196 let slot = shadow.querySelector("slot"); 197 let text = element.firstChild; 198 199 assert_true(tree.matches(":dir(ltr)"), "initial state (tree)"); 200 assert_true(element.matches(":dir(ltr)"), "initial state (element)"); 201 assert_true(slot.matches(":dir(ltr)"), "initial state (slot)"); 202 203 text.data = "\u05D0"; 204 205 assert_true(tree.matches(":dir(rtl)"), "state after first change (tree)"); 206 assert_true(element.matches(":dir(rtl)"), "state after first change (element)"); 207 assert_true(slot.matches(":dir(rtl)"), "state after first change (slot)"); 208 209 text.data = ""; 210 211 assert_true(tree.matches(":dir(rtl)"), "state after second change (tree)"); 212 assert_true(element.matches(":dir(rtl)"), "state after second change (element)"); 213 assert_true(slot.matches(":dir(rtl)"), "state after second change (slot)"); 214 215 tree.remove(); 216 }, "slotted non-HTML elements"); 217 218 test(() => { 219 let [tree, shadow] = setup_tree(` 220 <div> 221 <div id="root"> 222 <!-- element goes here --> 223 \u05D0 224 </div> 225 </div> 226 `, ` 227 <div dir="ltr"> 228 <slot></slot> 229 </div> 230 `); 231 232 let element = document.createElementNS("namespace", "element"); 233 let text = document.createTextNode("A"); 234 element.appendChild(text); 235 tree.querySelector("#root").prepend(element); 236 237 assert_not_equals(element.namespaceURI, tree.namespaceURI); 238 239 assert_true(tree.matches(":dir(ltr)"), "initial state (tree)"); 240 assert_true(element.matches(":dir(ltr)"), "initial state (element)"); 241 242 tree.dir = "auto"; 243 244 assert_true(tree.matches(":dir(ltr)"), "state after making dir=auto (tree)"); 245 assert_true(element.matches(":dir(ltr)"), "state after making dir=auto (element)"); 246 247 text.data = "\u05D0"; 248 249 assert_true(tree.matches(":dir(rtl)"), "state after first change (tree)"); 250 assert_true(element.matches(":dir(rtl)"), "state after first change (element)"); 251 252 text.data = ""; 253 254 assert_true(tree.matches(":dir(rtl)"), "state after second change (tree)"); 255 assert_true(element.matches(":dir(rtl)"), "state after second change (element)"); 256 257 tree.remove(); 258 }, "slotted non-HTML elements after dynamically assigning dir=auto, and dir attribute ignored on non-HTML elements"); 259 260 test(() => { 261 let e1 = setup_tree(` 262 <div dir=auto> 263 <div dir=ltr> 264 \u05D0 265 </div> 266 </div> 267 `); 268 let e2 = e1.firstElementChild; 269 assert_true(e1.matches(":dir(ltr)"), "parent is LTR before changes"); 270 assert_true(e2.matches(":dir(ltr)"), "child is LTR before changes"); 271 e2.removeAttribute("dir"); 272 assert_false(e1.matches(":dir(ltr)"), "parent is RTL after removing dir from child"); 273 assert_false(e2.matches(":dir(ltr)"), "child is RTL after removing dir from child"); 274 }, "dir=auto ancestor considers text in subtree after removing dir=ltr from it"); 275 276 test(() => { 277 let tree1, shadow1; 278 [tree1, shadow1] = setup_tree(` 279 <div> 280 <div id="root" dir="auto"> 281 <div id="root2"> 282 <span>A</span> 283 </div> 284 </div> 285 </div> 286 `,` 287 <slot dir="auto"></slot> 288 `); 289 let tree2 = tree1.querySelector("#root2"); 290 let shadow2 = tree2.attachShadow({mode: 'open'}); 291 shadow2.innerHTML = '<slot dir="auto"></slot>'; 292 293 let slot1 = shadow1.querySelector("slot"); 294 let slot2 = shadow2.querySelector("slot"); 295 let span = tree1.querySelector("span"); 296 297 // span slotted in slot2 hosted in root2 slotted in slot1 298 // span thus impacts auto-dir of two slots 299 assert_true(slot1.matches(":dir(ltr)", "outer slot initially ltr")); 300 assert_true(slot2.matches(":dir(ltr)", "inner slot initially ltr")); 301 span.innerHTML = "\u05D0"; 302 assert_true(slot1.matches(":dir(rtl)", "outer slot changed to rtl")); 303 assert_true(slot2.matches(":dir(rtl)", "inner slot changed to rtl")); 304 305 tree1.remove(); 306 }, 'Slotted content affects multiple dir=auto slots'); 307 308 test(() => { 309 let [tree, shadow] = setup_tree(` 310 <div> 311 <div id="root"> 312 <span>اختبر</span> 313 </div> 314 </div> 315 `, ` 316 <slot dir="auto"></slot> 317 `); 318 319 let slot = shadow.querySelector("slot"); 320 assert_equals(html_direction(slot), "rtl", "slot initially rtl"); 321 let span = tree.querySelector("span"); 322 span.remove(); 323 assert_equals(html_direction(slot), "ltr", "slot is reset to ltr"); 324 tree.remove(); 325 }, 'Removing slotted content resets direction on dir=auto slot'); 326 327 test(() => { 328 let [tree, shadow] = setup_tree(` 329 <div> 330 <div id=root> 331 <div> 332 <span>اختبر</span> 333 </div> 334 </div> 335 </div> 336 `,` 337 <slot dir=auto></slot> 338 `); 339 340 let slot = shadow.querySelector("slot"); 341 assert_equals(html_direction(slot), "rtl", "slot initially rtl"); 342 let span = tree.querySelector("span"); 343 span.remove(); 344 assert_equals(html_direction(slot), "ltr", "slot is reset to ltr"); 345 tree.remove(); 346 }, 'Removing child of slotted content changes direction on dir=auto slot'); 347 348 test(() => { 349 let tree; 350 tree = setup_tree(` 351 <div> 352 <span>اختبر</span> 353 <p>Text</p> 354 </div> 355 `); 356 let p = tree.querySelector("p"); 357 assert_true(p.matches(":dir(ltr)"), "child initially ltr"); 358 tree.dir = "auto"; 359 assert_true(p.matches(":dir(rtl)"), "child updated to rtl"); 360 tree.remove(); 361 }, 'Child directionality gets updated when dir=auto is set on parent'); 362 363 test(() => { 364 let [tree, shadow] = setup_tree(` 365 <div> 366 <div id=root> 367 <input value="اختبر"> 368 </div> 369 </div> 370 `,` 371 <slot dir=auto></slot> 372 `); 373 let slot = shadow.querySelector("slot"); 374 assert_equals(html_direction(slot), "ltr"); 375 tree.remove(); 376 }, 'dir=auto slot is not affected by text in value of input element children'); 377 378 test(() => { 379 let tree = setup_tree(` 380 <div> 381 <input dir="auto" value="اختبر"> 382 </div> 383 `); 384 let inp = tree.querySelector("input"); 385 assert_equals(html_direction(inp), "rtl"); 386 inp.type = "number"; 387 assert_equals(html_direction(inp), "ltr"); 388 tree.remove(); 389 }, 'input direction changes if it stops being auto-directionality form-associated'); 390 391 test(() => { 392 let [tree, shadow] = setup_tree(` 393 <div> 394 <div id=root dir=ltr> 395 <span>اختبر</span> 396 </div> 397 </div> 398 `,` 399 <div dir=auto id=container> 400 <slot></slot> 401 </div> 402 `); 403 let div = shadow.querySelector("#container"); 404 let host = tree.querySelector("#root"); 405 assert_equals(html_direction(div), 'ltr', 'ltr inherited from host despite rtl content'); 406 // set rtl on host directly, test it is propagated through slots 407 host.dir = "rtl"; 408 assert_equals(html_direction(div), 'rtl', 'host dir change propagated via slot'); 409 host.dir = ""; 410 assert_equals(html_direction(host), 'ltr', 'host dir reset to ltr'); 411 assert_equals(html_direction(div), 'ltr', 'host dir change propagated via slot'); 412 // host inherits rtl from parent, test it is still propagated through slots 413 tree.dir = "rtl"; 414 assert_equals(html_direction(host), 'rtl', 'host inherited rtl from parent'); 415 assert_equals(html_direction(div), 'rtl', 'host dir change propagated via slot'); 416 tree.remove(); 417 }, 'slot provides updated directionality from host to a dir=auto container'); 418 419 test(() => { 420 let input = setup_tree(`<input type="text">`); 421 assert_equals(html_direction(input), "ltr", "initial direction of input"); 422 input.value = "\u05D0"; 423 assert_equals(html_direction(input), "ltr", "direction of input with RTL contents"); 424 input.dir = "auto"; 425 assert_equals(html_direction(input), "rtl", "direction of input dir=auto with RTL contents"); 426 input.value = "a"; 427 assert_equals(html_direction(input), "ltr", "direction of input dir=auto with LTR contents"); 428 input.dir = "rtl"; 429 assert_equals(html_direction(input), "rtl", "direction of input dir=rtl with LTR contents"); 430 input.value = "ab"; 431 assert_equals(html_direction(input), "rtl", "direction of input dir=rtl with LTR contents (2)"); 432 input.value = "\u05D0"; 433 assert_equals(html_direction(input), "rtl", "direction of input dir=rtl with RTL contents"); 434 435 let textarea = setup_tree(`<textarea dir="auto"></textarea>`); 436 assert_equals(html_direction(textarea), "ltr", "direction of textarea dir=auto with empty contents"); 437 textarea.value = "a"; 438 assert_equals(html_direction(textarea), "ltr", "direction of textarea dir=auto with LTR contents"); 439 textarea.value = "\u05D0"; 440 assert_equals(html_direction(textarea), "rtl", "direction of textarea dir=auto with RTL contents"); 441 textarea.dir = "rtl"; 442 assert_equals(html_direction(textarea), "rtl", "direction of textarea dir=rtl with RTL contents"); 443 textarea.value = "a"; 444 assert_equals(html_direction(textarea), "rtl", "direction of textarea dir=rtl with LTR contents"); 445 }, 'text input and textarea value changes should only be reflected in :dir() when dir=auto (value changes)');