function setup_tree(light_tree, shadow_tree) { let body = document.body; let old_length = body.childNodes.length; body.insertAdjacentHTML("beforeend", light_tree.trim()); if (body.childNodes.length != old_length + 1) { throw "unexpected markup"; } let result = body.lastChild; if (shadow_tree) { let shadow = result.querySelector("#root").attachShadow({mode: "open"}); shadow.innerHTML = shadow_tree.trim(); return [result, shadow]; } return result; } test(t => { let a = setup_tree(`
hello
`); let acs = getComputedStyle(a); assert_true(a.matches(":dir(ltr)"), ":dir(ltr) matches before insertion"); assert_false(a.matches(":dir(rtl)"), ":dir(rtl) does not match before insertion"); assert_equals(acs.direction, "ltr", "CSSdirection before insertion"); b.innerHTML = "\u05D0"; assert_false(a.matches(":dir(ltr)"), ":dir(ltr) does not match after insertion"); assert_true(a.matches(":dir(rtl)"), ":dir(rtl) matches after insertion"); assert_equals(acs.direction, "rtl", "CSSdirection after insertion"); a.remove(); }, "dynamic insertion of RTL text in a child element"); test(() => { let div_rtlchar = document.createElement("div"); div_rtlchar.innerHTML = "\u05D0"; let container1 = document.createElement("div"); document.body.appendChild(container1); let container2 = document.createElement("div"); for (let container of [container1, container2]) { container.dir = "auto"; assert_true(container.matches(":dir(ltr)")); container.appendChild(div_rtlchar); assert_false(container.matches(":dir(ltr)")); div_rtlchar.remove(); assert_true(container.matches(":dir(ltr)")); } container1.remove(); }, "dir=auto changes for content insertion and removal, in and out of document"); test(() => { let tree, shadow; [tree, shadow] = setup_tree(`
A \u05D0
`, ` \u05D0 `); let one = shadow.getElementById("one"); let two = shadow.getElementById("two"); let l = tree.querySelector("#l"); let r = tree.querySelector("#r"); assert_false(one.matches(":dir(ltr)"), "#one while empty"); assert_true(two.matches(":dir(ltr)"), "#two with both spans"); l.slot = "one"; assert_true(one.matches(":dir(ltr)"), "#one with LTR child span"); assert_false(two.matches(":dir(ltr)"), "#two with RTL child span"); r.slot = "one"; assert_true(one.matches(":dir(ltr)"), "#one with both child spans"); assert_true(two.matches(":dir(ltr)"), "#two while empty"); l.slot = ""; assert_false(one.matches(":dir(ltr)"), "#one with RTL child span"); assert_true(two.matches(":dir(ltr)"), "#two with LTR child span"); tree.remove(); }, "dir=auto changes for slot reassignment"); test(() => { let tree, shadow; [tree, shadow] = setup_tree(`
A
`, `
`); let text = tree.querySelector("#text"); let slot = shadow.querySelector("#slot"); assert_true(tree.matches(":dir(ltr)"), "node tree ancestor before first text change"); assert_true(slot.matches(":dir(ltr)"), "slot before first text change"); text.innerText = "\u05D0"; assert_false(tree.matches(":dir(ltr)"), "node tree ancestor after first text change"); assert_false(slot.matches(":dir(ltr)"), "slot after first text change"); tree.dir = "rtl"; assert_false(tree.matches(":dir(ltr)"), "node tree ancestor before second text change"); assert_false(slot.matches(":dir(ltr)"), "slot before second text change"); text.innerText = "A"; assert_false(tree.matches(":dir(ltr)"), "node tree ancestor after second text change"); assert_true(slot.matches(":dir(ltr)"), "slot after second text change"); slot.dir = "ltr"; assert_false(tree.matches(":dir(ltr)"), "node tree ancestor before third text change"); assert_true(slot.matches(":dir(ltr)"), "slot before third text change"); text.innerText = "\u05D0"; assert_false(tree.matches(":dir(ltr)"), "node tree ancestor after third text change"); assert_true(slot.matches(":dir(ltr)"), "slot after third text change"); slot.dir = "auto"; tree.dir = "auto"; assert_false(tree.matches(":dir(ltr)"), "node tree ancestor after fourth text change"); assert_false(slot.matches(":dir(ltr)"), "slot after fourth text change"); text.innerText = "A"; assert_true(tree.matches(":dir(ltr)"), "node tree ancestor before fourth text change"); assert_true(slot.matches(":dir(ltr)"), "slot before fourth text change"); slot.dir = "rtl"; assert_true(tree.matches(":dir(ltr)"), "node tree ancestor before fifth text change"); assert_false(slot.matches(":dir(ltr)"), "slot before fifth text change"); text.innerText = "\u05D0"; assert_false(tree.matches(":dir(ltr)"), "node tree ancestor before fifth text change"); assert_false(slot.matches(":dir(ltr)"), "slot before fifth text change"); tree.remove(); }, "text changes affecting both slot and ancestor with dir=auto"); test(() => { let tree = setup_tree(`
A \u05D0 A \u05D0
`); let a1 = tree.querySelector("#a1"); let aleph1 = tree.querySelector("#aleph1"); assert_true(tree.matches(":dir(ltr)"), "initial state"); assert_false(tree.matches(":dir(rtl)"), "initial state"); a1.dir = "ltr"; assert_false(tree.matches(":dir(ltr)"), "after change 1"); a1.dir = "invalid"; assert_true(tree.matches(":dir(ltr)"), "after change 2"); a1.dir = "rtl"; assert_false(tree.matches(":dir(ltr)"), "after change 3"); a1.removeAttribute("dir"); assert_true(tree.matches(":dir(ltr)"), "after change 4"); a1.dir = "invalid"; assert_true(tree.matches(":dir(ltr)"), "after change 5"); a1.dir = "rtl"; assert_false(tree.matches(":dir(ltr)"), "after change 6"); aleph1.dir = "auto"; assert_true(tree.matches(":dir(ltr)"), "after change 7"); aleph1.dir = "invalid"; assert_false(tree.matches(":dir(ltr)"), "after change 8"); tree.remove(); }, "dynamic changes to subtrees excluded as a result of the dir attribute"); test(() => { let tree = setup_tree(`
`); let element = document.createElementNS("namespace", "element"); let text = document.createTextNode("\u05D0"); element.appendChild(text); tree.prepend(element); assert_not_equals(element.namespaceURI, tree.namespaceURI); assert_true(tree.matches(":dir(rtl)"), "initial state"); assert_false(tree.matches(":dir(ltr)"), "initial state"); text.data = "A"; assert_true(tree.matches(":dir(ltr)"), "after dynamic change"); assert_false(tree.matches(":dir(rtl)"), "after dynamic change"); tree.remove(); }, "dynamic changes inside of non-HTML elements"); test(() => { let tree, shadow; [tree, shadow] = setup_tree(`
A \u05D0
`, `
\u05D0
`); let element = tree.querySelector("element"); let slot = shadow.querySelector("slot"); let text = element.firstChild; assert_true(tree.matches(":dir(ltr)"), "initial state (tree)"); assert_true(element.matches(":dir(ltr)"), "initial state (element)"); assert_true(slot.matches(":dir(ltr)"), "initial state (slot)"); text.data = "\u05D0"; assert_true(tree.matches(":dir(rtl)"), "state after first change (tree)"); assert_true(element.matches(":dir(rtl)"), "state after first change (element)"); assert_true(slot.matches(":dir(rtl)"), "state after first change (slot)"); text.data = ""; assert_true(tree.matches(":dir(rtl)"), "state after second change (tree)"); assert_true(element.matches(":dir(rtl)"), "state after second change (element)"); assert_true(slot.matches(":dir(rtl)"), "state after second change (slot)"); tree.remove(); }, "slotted non-HTML elements"); test(() => { let tree, shadow; [tree, shadow] = setup_tree(`
\u05D0
`, `
`); let element = document.createElementNS("namespace", "element"); let text = document.createTextNode("A"); element.appendChild(text); tree.querySelector("#root").prepend(element); assert_not_equals(element.namespaceURI, tree.namespaceURI); assert_true(tree.matches(":dir(ltr)"), "initial state (tree)"); assert_true(element.matches(":dir(ltr)"), "initial state (element)"); tree.dir = "auto"; assert_true(tree.matches(":dir(ltr)"), "state after making dir=auto (tree)"); assert_true(element.matches(":dir(ltr)"), "state after making dir=auto (element)"); text.data = "\u05D0"; assert_true(tree.matches(":dir(rtl)"), "state after first change (tree)"); assert_true(element.matches(":dir(rtl)"), "state after first change (element)"); text.data = ""; assert_true(tree.matches(":dir(rtl)"), "state after second change (tree)"); assert_true(element.matches(":dir(rtl)"), "state after second change (element)"); tree.remove(); }, "slotted non-HTML elements after dynamically assigning dir=auto, and dir attribute ignored on non-HTML elements");