| <!doctype html> |
| <meta charset="utf-8" /> |
| <meta name="author" title="Keith Cirkel" href="mailto:wpt@keithcirkel.co.uk" /> |
| <link rel="help" href="https://github.com/whatwg/html/pull/11086" /> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| |
| <div> |
| <h1 id="testMutation"></h1> |
| </div> |
| |
| <div id="testNestedOuter"> |
| <div id="testNestedInner"> |
| <h1 id="testNested"></h1> |
| </div> |
| </div> |
| |
| <div id="testResetMutationContainer" headingoffset="3"> |
| <h1 id="testResetMutation"></h1> |
| </div> |
| |
| <div id="testResetInteractionOuter" headingoffset="5"> |
| <div id="testResetInteractionInner"> |
| <h1 id="testResetInteraction"></h1> |
| </div> |
| </div> |
| |
| <div id="testAllHeadingsContainer"> |
| <h1 id="testAllH1"></h1> |
| <h2 id="testAllH2"></h2> |
| <h3 id="testAllH3"></h3> |
| <h4 id="testAllH4"></h4> |
| <h5 id="testAllH5"></h5> |
| <h6 id="testAllH6"></h6> |
| </div> |
| |
| <div id="testDeepNestingContainer"> |
| <div> |
| <div> |
| <h1 id="testDeepNesting"></h1> |
| </div> |
| </div> |
| </div> |
| |
| <div id="testShadowHost"> |
| <template shadowrootmode="open"> |
| <h1 id="testShadowHostH1"></h1> |
| </template> |
| </div> |
| |
| <div id="testShadowAncestor"> |
| <div id="testShadowAncestorHost"> |
| <template shadowrootmode="open"> |
| <h1 id="testShadowAncestorH1"></h1> |
| </template> |
| </div> |
| </div> |
| |
| <div id="testShadowInner"> |
| <template shadowrootmode="open"> |
| <div id="testShadowInnerContainer"> |
| <h1 id="testShadowInnerH1"></h1> |
| </div> |
| </template> |
| </div> |
| |
| <div id="testSlotted"> |
| <template shadowrootmode="open"> |
| <slot id="testSlottedSlot"></slot> |
| </template> |
| <h1 id="testSlottedH1"></h1> |
| </div> |
| |
| <div id="testSlottedNested"> |
| <template shadowrootmode="open"> |
| <slot id="testSlottedNestedSlot"></slot> |
| </template> |
| <div id="testSlottedNestedChild"> |
| <template shadowrootmode="open"> |
| <h1 id="testSlottedNestedH1"></h1> |
| </template> |
| </div> |
| </div> |
| |
| <div> |
| <h1 id="testClamping"></h1> |
| </div> |
| |
| <div> |
| <h1 id="testInvalid"></h1> |
| </div> |
| |
| <div id="testSlotNameChange"> |
| <template shadowrootmode="open"> |
| <div headingoffset="2"> |
| <slot name="a" id="testSlotNameChangeSlotA"></slot> |
| </div> |
| <div headingoffset="4"> |
| <slot name="b" id="testSlotNameChangeSlotB"></slot> |
| </div> |
| </template> |
| <h1 slot="a" id="testSlotNameChangeH1"></h1> |
| </div> |
| |
| <div id="testSlotAttrChange"> |
| <template shadowrootmode="open"> |
| <div headingoffset="1"> |
| <slot name="x" id="testSlotAttrChangeSlotX"></slot> |
| </div> |
| <div headingoffset="3"> |
| <slot name="y" id="testSlotAttrChangeSlotY"></slot> |
| </div> |
| </template> |
| <h1 slot="x" id="testSlotAttrChangeH1"></h1> |
| </div> |
| |
| <div id="testSlotRemoval"> |
| <template shadowrootmode="open"> |
| <div headingoffset="5"> |
| <slot id="testSlotRemovalSlot"></slot> |
| </div> |
| </template> |
| <h1 id="testSlotRemovalH1"></h1> |
| </div> |
| |
| <div id="testSlotDefault"> |
| <template shadowrootmode="open"> |
| <div headingoffset="2"> |
| <slot id="testSlotDefaultSlot"></slot> |
| </div> |
| </template> |
| <h1 id="testSlotDefaultH1"></h1> |
| </div> |
| |
| <div id="testSlotMultiple"> |
| <template shadowrootmode="open"> |
| <div headingoffset="1"> |
| <slot name="first" id="testSlotMultipleSlotFirst"></slot> |
| </div> |
| <div headingoffset="3"> |
| <slot name="second" id="testSlotMultipleSlotSecond"></slot> |
| </div> |
| </template> |
| <h1 slot="first" id="testSlotMultipleH1A"></h1> |
| <h1 slot="second" id="testSlotMultipleH1B"></h1> |
| </div> |
| |
| <div id="testNestedShadow" headingoffset="1"> |
| <template shadowrootmode="open"> |
| <div headingoffset="2" id="testNestedShadowInner"> |
| <slot id="testNestedShadowSlot"></slot> |
| </div> |
| </template> |
| <div id="testNestedShadowChild"> |
| <template shadowrootmode="open"> |
| <div headingoffset="3" id="testNestedShadowChildInner"> |
| <h1 id="testNestedShadowH1"></h1> |
| </div> |
| </template> |
| </div> |
| </div> |
| |
| <div id="testTripleShadow"> |
| <template shadowrootmode="open"> |
| <div headingoffset="1" id="testTripleShadowL1"> |
| <slot id="testTripleShadowSlot1"></slot> |
| </div> |
| </template> |
| <div id="testTripleShadowChild1"> |
| <template shadowrootmode="open"> |
| <div headingoffset="1" id="testTripleShadowL2"> |
| <slot id="testTripleShadowSlot2"></slot> |
| </div> |
| </template> |
| <div id="testTripleShadowChild2"> |
| <template shadowrootmode="open"> |
| <h1 id="testTripleShadowH1"></h1> |
| </template> |
| </div> |
| </div> |
| </div> |
| |
| <div id="testSvgSlotted"> |
| <template shadowrootmode="open"> |
| <div headingoffset="2"> |
| <slot id="testSvgSlottedSlot"></slot> |
| </div> |
| </template> |
| </div> |
| |
| <div id="testNestedShadowHeadings"> |
| <template shadowrootmode="open"> |
| <div headingoffset="1" id="testNestedShadowHeadingsL1"> |
| <slot id="testNestedShadowHeadingsSlot"></slot> |
| <h1 id="testNestedShadowHeadingsInnerH1"></h1> |
| </div> |
| </template> |
| <div id="testNestedShadowHeadingsChild"> |
| <template shadowrootmode="open"> |
| <div headingoffset="2" id="testNestedShadowHeadingsL2"> |
| <h1 id="testNestedShadowHeadingsChildH1"></h1> |
| </div> |
| </template> |
| </div> |
| </div> |
| |
| <div id="testManualSlot"></div> |
| |
| <div id="testManualSlotMultiple"></div> |
| |
| <script> |
| const levels = [1, 2, 3, 4, 5, 6, 7, 8, 9]; |
| const matchAllLevels = (el) => levels.map((l) => el.matches(`:heading(${l})`)); |
| const assertLevel = (el, expected, msg) => { |
| const bools = matchAllLevels(el); |
| const expected_bools = levels.map((l) => l == expected); |
| assert_array_equals(bools, expected_bools, msg); |
| }; |
| |
| for (const style of ["property", "attribute"]) { |
| const set = (el, val) => { |
| if (style === "property") { |
| el.headingOffset = val; |
| } else { |
| el.setAttribute("headingoffset", String(val)); |
| } |
| }; |
| const clear = (el) => { |
| if (style === "property") { |
| el.headingOffset = 0; |
| } else { |
| el.removeAttribute("headingoffset"); |
| } |
| }; |
| |
| test(t => { |
| const container = testMutation.parentElement; |
| t.add_cleanup(() => clear(container)); |
| |
| assertLevel(testMutation, 1, "h1 starts at level 1"); |
| set(container, 2); |
| assertLevel(testMutation, 3, "h1 becomes level 3 after parent gets headingoffset=2"); |
| set(container, 5); |
| assertLevel(testMutation, 6, "h1 becomes level 6 after parent headingoffset changes to 5"); |
| clear(container); |
| assertLevel(testMutation, 1, "h1 returns to level 1 after parent headingoffset removed"); |
| }, `headingoffset ${style} mutations update descendant headings`); |
| |
| test(t => { |
| t.add_cleanup(() => { |
| clear(testNestedOuter); |
| clear(testNestedInner); |
| }); |
| |
| assertLevel(testNested, 1, "h1 starts at level 1"); |
| set(testNestedOuter, 1); |
| assertLevel(testNested, 2, "h1 becomes level 2 after outer headingOffset=1"); |
| set(testNestedInner, 2); |
| assertLevel(testNested, 4, "h1 becomes level 4 after inner headingOffset=2 (1+2+1)"); |
| set(testNestedOuter, 3); |
| assertLevel(testNested, 6, "h1 becomes level 6 after outer headingOffset changes to 3 (3+2+1)"); |
| clear(testNestedInner); |
| assertLevel(testNested, 4, "h1 becomes level 4 after inner headingOffset=0 (3+0+1)"); |
| }, `nested headingoffset ${style} mutations propagate to descendants`); |
| |
| test(t => { |
| t.add_cleanup(() => { testAllHeadingsContainer.headingOffset = 0; }); |
| |
| assertLevel(testAllH1, 1, "h1 starts at level 1"); |
| assertLevel(testAllH2, 2, "h2 starts at level 2"); |
| assertLevel(testAllH3, 3, "h3 starts at level 3"); |
| assertLevel(testAllH4, 4, "h4 starts at level 4"); |
| assertLevel(testAllH5, 5, "h5 starts at level 5"); |
| assertLevel(testAllH6, 6, "h6 starts at level 6"); |
| |
| set(testAllHeadingsContainer, 2); |
| |
| assertLevel(testAllH1, 3, "h1 becomes level 3"); |
| assertLevel(testAllH2, 4, "h2 becomes level 4"); |
| assertLevel(testAllH3, 5, "h3 becomes level 5"); |
| assertLevel(testAllH4, 6, "h4 becomes level 6"); |
| assertLevel(testAllH5, 7, "h5 becomes level 7"); |
| assertLevel(testAllH6, 8, "h6 becomes level 8"); |
| }, `headingoffset ${style} mutations update all heading types`); |
| |
| test(t => { |
| t.add_cleanup(() => clear(testDeepNestingContainer)); |
| |
| assertLevel(testDeepNesting, 1, "deeply nested h1 starts at level 1"); |
| set(testDeepNestingContainer, 3); |
| assertLevel(testDeepNesting, 4, "deeply nested h1 becomes level 4 after ancestor mutation"); |
| }, `headingoffset ${style} mutations propagate to deeply nested desendants`); |
| |
| test(t => { |
| const h1 = testShadowHost.shadowRoot.getElementById("testShadowHostH1"); |
| t.add_cleanup(() => clear(testShadowHost)); |
| |
| assertLevel(h1, 1, "h1 in shadow DOM starts at level 1"); |
| set(testShadowHost, 2); |
| assertLevel(h1, 3, "h1 in shadow DOM becomes level 3 after host headingoffset=2"); |
| clear(testShadowHost); |
| assertLevel(h1, 1, "h1 in shadow DOM returns to level 1 after host headingoffset=0"); |
| }, `headingoffset ${style} mutations on shadow host update shadow DOM headings`); |
| |
| test(t => { |
| const h1 = testShadowAncestorHost.shadowRoot.getElementById("testShadowAncestorH1"); |
| t.add_cleanup(() => { |
| clear(testShadowAncestor); |
| clear(testShadowAncestorHost); |
| }); |
| |
| assertLevel(h1, 1, "h1 in shadow DOM starts at level 1"); |
| set(testShadowAncestor, 2); |
| assertLevel(h1, 3, "h1 in shadow DOM becomes level 3 after outer headingoffset=2"); |
| set(testShadowAncestorHost, 1); |
| assertLevel(h1, 4, "h1 in shadow DOM becomes level 4 after host headingoffset=1 (2+1+1)"); |
| }, `headingoffset ${style} mutations on ancestors of shadow host propagate to shadow DOM`); |
| |
| test(t => { |
| const innerContainer = testShadowInner.shadowRoot.getElementById("testShadowInnerContainer"); |
| const h1 = testShadowInner.shadowRoot.getElementById("testShadowInnerH1"); |
| t.add_cleanup(() => { |
| clear(testShadowInner); |
| clear(innerContainer); |
| }); |
| |
| assertLevel(h1, 1, "h1 starts at level 1"); |
| set(innerContainer, 2); |
| assertLevel(h1, 3, "h1 becomes level 3 after shadow DOM container headingoffset=2"); |
| set(testShadowInner, 1); |
| assertLevel(h1, 4, "h1 becomes level 4 after host headingoffset=1 (1+2+1)"); |
| }, `headingoffset ${style} mutations inside shadow DOM update correctly`); |
| |
| test(t => { |
| const slot = testSlotted.shadowRoot.getElementById("testSlottedSlot"); |
| t.add_cleanup(() => { |
| clear(testSlotted); |
| clear(slot); |
| }); |
| |
| assertLevel(testSlottedH1, 1, "slotted h1 starts at level 1"); |
| set(slot, 2); |
| assertLevel(testSlottedH1, 1, "slot headingoffset=2 does not affect slotted h1"); |
| set(testSlotted, 1); |
| assertLevel(testSlottedH1, 2, "slotted h1 becomes level 2 after host headingoffset=1"); |
| }, `headingoffset ${style} mutations on host (not slot) update slotted headings`); |
| |
| test(t => { |
| const slot = testSlottedNested.shadowRoot.getElementById("testSlottedNestedSlot"); |
| const h1 = testSlottedNestedChild.shadowRoot.getElementById("testSlottedNestedH1"); |
| t.add_cleanup(() => { |
| clear(testSlottedNested); |
| clear(slot); |
| }); |
| |
| assertLevel(h1, 1, "h1 in nested shadow starts at level 1"); |
| set(slot, 2); |
| assertLevel(h1, 1, "outer slot headingoffset=2 does not affect h1"); |
| set(testSlottedNested, 1); |
| assertLevel(h1, 2, "h1 becomes level 2 after outermost host headingoffset=1"); |
| }, `headingoffset ${style} mutations propagate via host chain through nested shadow roots`); |
| |
| test(t => { |
| const container = testClamping.parentElement; |
| t.add_cleanup(() => clear(container)); |
| |
| assertLevel(testClamping, 1, "h1 starts at level 1"); |
| set(container, 10); |
| assertLevel(testClamping, 9, "h1 clamped to level 9 with headingoffset=10"); |
| set(container, 100); |
| assertLevel(testClamping, 9, "h1 still clamped to level 9 with headingoffset=100"); |
| }, `headingoffset ${style} mutations respect level clamping`); |
| |
| test(t => { |
| const innerDiv = testNestedShadow.shadowRoot.getElementById("testNestedShadowInner"); |
| const innerShadowDiv = testNestedShadowChild.shadowRoot.getElementById("testNestedShadowChildInner"); |
| const h1 = testNestedShadowChild.shadowRoot.getElementById("testNestedShadowH1"); |
| t.add_cleanup(() => { |
| set(testNestedShadow, 1); |
| set(innerDiv, 2); |
| set(innerShadowDiv, 3); |
| }); |
| |
| assertLevel(h1, 5, "h1 in nested shadow starts at level 5 (3+1+1)"); |
| set(testNestedShadow, 0); |
| assertLevel(h1, 4, "h1 becomes level 4 after outermost headingoffset=0 (3+0+1)"); |
| set(innerDiv, 0); |
| assertLevel(h1, 4, "outer shadow's slot wrapper does not affect h1"); |
| set(innerShadowDiv, 0); |
| assertLevel(h1, 1, "h1 becomes level 1 after innermost wrapper headingoffset=0"); |
| set(testNestedShadow, 2); |
| assertLevel(h1, 3, "h1 becomes level 3 after outermost headingoffset=2 (0+2+1)"); |
| }, `headingoffset ${style} mutations propagate via host chain across two levels of shadow DOM`); |
| |
| test(t => { |
| const l1 = testTripleShadow.shadowRoot.getElementById("testTripleShadowL1"); |
| const l2 = testTripleShadowChild1.shadowRoot.getElementById("testTripleShadowL2"); |
| const h1 = testTripleShadowChild2.shadowRoot.getElementById("testTripleShadowH1"); |
| t.add_cleanup(() => { |
| set(l1, 1); |
| set(l2, 1); |
| clear(testTripleShadow); |
| clear(testTripleShadowChild1); |
| }); |
| |
| assertLevel(h1, 1, "h1 in triple shadow starts at level 1"); |
| set(l1, 3); |
| assertLevel(h1, 1, "l1 in outer shadow tree does not affect h1"); |
| set(l2, 2); |
| assertLevel(h1, 1, "l2 in middle shadow tree does not affect h1"); |
| set(testTripleShadow, 2); |
| assertLevel(h1, 3, "h1 becomes level 3 after outermost host headingoffset=2 (2+1)"); |
| set(testTripleShadowChild1, 1); |
| assertLevel(h1, 4, "h1 becomes level 4 after middle host headingoffset=1 (2+1+1)"); |
| clear(testTripleShadow); |
| assertLevel(h1, 2, "h1 becomes level 2 after outermost cleared (1+1)"); |
| }, `headingoffset ${style} propagates only via host chain through three nested shadow DOMs`); |
| |
| test(t => { |
| const l1 = testNestedShadowHeadings.shadowRoot.getElementById("testNestedShadowHeadingsL1"); |
| const l2 = testNestedShadowHeadingsChild.shadowRoot.getElementById("testNestedShadowHeadingsL2"); |
| const innerH1 = testNestedShadowHeadings.shadowRoot.getElementById("testNestedShadowHeadingsInnerH1"); |
| const childH1 = testNestedShadowHeadingsChild.shadowRoot.getElementById("testNestedShadowHeadingsChildH1"); |
| t.add_cleanup(() => { |
| set(l1, 1); |
| set(l2, 2); |
| }); |
| |
| assertLevel(innerH1, 2, "inner h1 starts at level 2 (1+1)"); |
| assertLevel(childH1, 3, "child h1 starts at level 3 (2+1)"); |
| set(l1, 3); |
| assertLevel(innerH1, 4, "inner h1 becomes level 4 after l1 headingoffset=3 (3+1)"); |
| assertLevel(childH1, 3, "l1 in outer shadow tree does not affect child h1"); |
| set(l2, 0); |
| assertLevel(innerH1, 4, "inner h1 stays at level 4 (l2 change doesn't affect it)"); |
| assertLevel(childH1, 1, "child h1 becomes level 1 after l2 headingoffset=0"); |
| }, `headingoffset ${style} mutations update headings at multiple shadow DOM levels independently`); |
| } |
| |
| test(t => { |
| t.add_cleanup(() => { |
| testResetMutationContainer.headingOffset = 3; |
| testResetMutationContainer.headingReset = false; |
| }); |
| |
| assertLevel(testResetMutation, 4, "h1 starts at level 4 (3+1)"); |
| testResetMutationContainer.headingReset = true; |
| assertLevel(testResetMutation, 4, "h1 stays at level 4 after parent gets headingreset (still 3+1, but now reset)"); |
| testResetMutationContainer.headingOffset = 1; |
| assertLevel(testResetMutation, 2, "h1 becomes level 2 after parent headingoffset changes to 1"); |
| testResetMutationContainer.headingReset = false; |
| assertLevel(testResetMutation, 2, "h1 stays level 2 after headingreset removed (still 1+1)"); |
| }, "headingreset mutations update descendant headings"); |
| |
| test(t => { |
| t.add_cleanup(() => { |
| testResetInteractionOuter.headingOffset = 5; |
| testResetInteractionInner.headingOffset = 0; |
| testResetInteractionInner.headingReset = false; |
| }); |
| |
| assertLevel(testResetInteraction, 6, "h1 starts at level 6 (5+1)"); |
| testResetInteractionInner.headingReset = true; |
| assertLevel(testResetInteraction, 1, "h1 becomes level 1 after inner gets headingreset"); |
| testResetInteractionInner.headingOffset = 2; |
| assertLevel(testResetInteraction, 3, "h1 becomes level 3 after inner headingoffset=2 (2+1, reset blocks outer)"); |
| testResetInteractionInner.headingReset = false; |
| assertLevel(testResetInteraction, 8, "h1 becomes level 8 after inner headingreset removed (5+2+1)"); |
| }, "headingreset and headingoffset mutations interact correctly"); |
| |
| test(t => { |
| const container = testInvalid.parentElement; |
| t.add_cleanup(() => container.removeAttribute("headingoffset")); |
| |
| container.headingOffset = 2; |
| assertLevel(testInvalid, 3, "h1 at level 3 with headingoffset=2"); |
| assert_equals(container.headingOffset, 2, "headingOffset property returns 2"); |
| |
| container.setAttribute("headingoffset", "invalid"); |
| assertLevel(testInvalid, 1, "h1 returns to level 1 with invalid headingoffset"); |
| assert_equals(container.headingOffset, 0, "headingOffset proprety returns 0 for invalid value"); |
| |
| container.setAttribute("headingoffset", "3"); |
| assertLevel(testInvalid, 4, "h1 at level 4 with headingoffset=3"); |
| assert_equals(container.headingOffset, 3, "headingOffset property returns 3"); |
| }, "invalid headingoffset attribute values are handled correctly on mutation"); |
| |
| test(t => { |
| const slotA = testSlotNameChange.shadowRoot.getElementById("testSlotNameChangeSlotA"); |
| const slotB = testSlotNameChange.shadowRoot.getElementById("testSlotNameChangeSlotB"); |
| t.add_cleanup(() => { |
| slotA.name = "a"; |
| testSlotNameChangeH1.slot = "a"; |
| testSlotNameChange.removeAttribute("headingoffset"); |
| }); |
| |
| assertLevel(testSlotNameChangeH1, 1, "h1 slotted into slot a starts at level 1"); |
| slotA.name = "c"; |
| assertLevel(testSlotNameChangeH1, 1, "h1 stays at level 1 when slot a renamed"); |
| slotB.name = "a"; |
| assertLevel(testSlotNameChangeH1, 1, "h1 stays at level 1 when reslotted into slot b"); |
| testSlotNameChange.setAttribute("headingoffset", "2"); |
| assertLevel(testSlotNameChangeH1, 3, "h1 becomes level 3 after host headingoffset=2"); |
| }, "slot name attribute changes do not change slotted heading levels"); |
| |
| test(t => { |
| t.add_cleanup(() => { |
| testSlotAttrChangeH1.slot = "x"; |
| testSlotAttrChange.removeAttribute("headingoffset"); |
| }); |
| |
| assertLevel(testSlotAttrChangeH1, 1, "h1 with slot=x starts at level 1"); |
| testSlotAttrChangeH1.slot = "y"; |
| assertLevel(testSlotAttrChangeH1, 1, "h1 stays at level 1 when moved to slot y"); |
| testSlotAttrChangeH1.slot = "nonexistent"; |
| assertLevel(testSlotAttrChangeH1, 1, "h1 stays at level 1 when slot points to nonexistent slot"); |
| testSlotAttrChangeH1.slot = "x"; |
| testSlotAttrChange.setAttribute("headingoffset", "3"); |
| assertLevel(testSlotAttrChangeH1, 4, "h1 becomes level 4 after host headingoffset=3"); |
| }, "element slot attribute changes do not change heading levels"); |
| |
| test(t => { |
| const slot = testSlotRemoval.shadowRoot.getElementById("testSlotRemovalSlot"); |
| const container = slot.parentElement; |
| t.add_cleanup(() => { |
| container.appendChild(slot); |
| testSlotRemoval.removeAttribute("headingoffset"); |
| }); |
| |
| assertLevel(testSlotRemovalH1, 1, "h1 starts at level 1"); |
| slot.remove(); |
| assertLevel(testSlotRemovalH1, 1, "h1 stays at level 1 when slot removed"); |
| container.appendChild(slot); |
| assertLevel(testSlotRemovalH1, 1, "h1 stays at level 1 when slot re-added"); |
| testSlotRemoval.setAttribute("headingoffset", "4"); |
| assertLevel(testSlotRemovalH1, 5, "h1 becomes level 5 after host headingoffset=4"); |
| }, "slot element removal and addition do not change slotted heading levels"); |
| |
| test(t => { |
| const slot = testSlotDefault.shadowRoot.getElementById("testSlotDefaultSlot"); |
| t.add_cleanup(() => { |
| slot.removeAttribute("name"); |
| testSlotDefaultH1.removeAttribute("slot"); |
| testSlotDefault.removeAttribute("headingoffset"); |
| }); |
| |
| assertLevel(testSlotDefaultH1, 1, "h1 in default slot starts at level 1"); |
| slot.name = "named"; |
| assertLevel(testSlotDefaultH1, 1, "h1 stays at level 1 when default slot becomes named"); |
| testSlotDefaultH1.slot = "named"; |
| assertLevel(testSlotDefaultH1, 1, "h1 stays at level 1 when explicitly assigned to named slot"); |
| slot.removeAttribute("name"); |
| assertLevel(testSlotDefaultH1, 1, "h1 stays at level 1 when slot becomes default but h1 has explicit slot attr"); |
| testSlotDefaultH1.removeAttribute("slot"); |
| testSlotDefault.setAttribute("headingoffset", "2"); |
| assertLevel(testSlotDefaultH1, 3, "h1 becomes level 3 after host headingoffset=2"); |
| }, "default-to-named slot transitions do not change heading levels"); |
| |
| test(t => { |
| const slotFirst = testSlotMultiple.shadowRoot.getElementById("testSlotMultipleSlotFirst"); |
| const slotSecond = testSlotMultiple.shadowRoot.getElementById("testSlotMultipleSlotSecond"); |
| t.add_cleanup(() => { |
| slotFirst.name = "first"; |
| slotSecond.name = "second"; |
| testSlotMultiple.removeAttribute("headingoffset"); |
| }); |
| |
| assertLevel(testSlotMultipleH1A, 1, "h1 A in slot first starts at level 1"); |
| assertLevel(testSlotMultipleH1B, 1, "h1 B in slot second starts at level 1"); |
| |
| slotFirst.name = "second"; |
| slotSecond.name = "first"; |
| |
| assertLevel(testSlotMultipleH1A, 1, "h1 A stays at level 1 after slot swap"); |
| assertLevel(testSlotMultipleH1B, 1, "h1 B stays at level 1 after slot swap"); |
| |
| testSlotMultiple.setAttribute("headingoffset", "2"); |
| |
| assertLevel(testSlotMultipleH1A, 3, "h1 A becomes level 3 after host headingoffset=2"); |
| assertLevel(testSlotMultipleH1B, 3, "h1 B becomes level 3 after host headingoffset=2"); |
| }, "swapping slot names does not change slotted heading levels"); |
| |
| test(t => { |
| const shadow = testManualSlot.attachShadow({ mode: "open", slotAssignment: "manual" }); |
| const slotContainer = document.createElement("div"); |
| slotContainer.setAttribute("headingoffset", "2"); |
| const slot = document.createElement("slot"); |
| slotContainer.appendChild(slot); |
| shadow.appendChild(slotContainer); |
| const h1 = document.createElement("h1"); |
| const div = document.createElement("div"); |
| const divH1 = document.createElement("h1"); |
| div.appendChild(divH1); |
| testManualSlot.appendChild(h1); |
| testManualSlot.appendChild(div); |
| t.add_cleanup(() => { |
| slot.assign(); |
| h1.remove(); |
| div.remove(); |
| testManualSlot.removeAttribute("headingoffset"); |
| }); |
| |
| assertLevel(h1, 1, "h1 starts at level 1"); |
| assertLevel(divH1, 1, "h1 inside div starts at level 1"); |
| |
| slot.assign(h1); |
| assertLevel(h1, 1, "h1 stays at level 1 after manual slot assignment"); |
| |
| slot.assign(div); |
| assertLevel(h1, 1, "h1 stays at level 1 after unassignment"); |
| assertLevel(divH1, 1, "h1 inside div stays at level 1 after div manually assigned"); |
| |
| testManualSlot.setAttribute("headingoffset", "1"); |
| assertLevel(divH1, 2, "h1 inside manually-assigned div becomes level 2 after host headingoffset=1"); |
| |
| slot.assign(); |
| assertLevel(divH1, 2, "h1 inside div stays at level 2 after slot cleared"); |
| }, "manual slot assignment does not change heading levels"); |
| |
| test(t => { |
| const shadow = testManualSlotMultiple.attachShadow({ mode: "open", slotAssignment: "manual" }); |
| const divA = document.createElement("div"); |
| divA.setAttribute("headingoffset", "1"); |
| const slotA = document.createElement("slot"); |
| divA.appendChild(slotA); |
| shadow.appendChild(divA); |
| const divB = document.createElement("div"); |
| divB.setAttribute("headingoffset", "3"); |
| const slotB = document.createElement("slot"); |
| divB.appendChild(slotB); |
| shadow.appendChild(divB); |
| const h1 = document.createElement("h1"); |
| testManualSlotMultiple.appendChild(h1); |
| t.add_cleanup(() => { |
| slotA.assign(); |
| slotB.assign(); |
| h1.remove(); |
| testManualSlotMultiple.removeAttribute("headingoffset"); |
| }); |
| |
| assertLevel(h1, 1, "h1 starts at level 1 (not assigned)"); |
| slotA.assign(h1); |
| assertLevel(h1, 1, "h1 stays at level 1 after assigned to slot A"); |
| slotB.assign(h1); |
| assertLevel(h1, 1, "h1 stays at level 1 after reassigned to slot B"); |
| slotA.assign(h1); |
| assertLevel(h1, 1, "h1 stays at level 1 after reassigned back to slot A"); |
| testManualSlotMultiple.setAttribute("headingoffset", "2"); |
| assertLevel(h1, 3, "h1 becomes level 3 after host headingoffset=2"); |
| }, "manual slot reassignment between slots does not change heading levels"); |
| |
| test(t => { |
| const slot = testSvgSlotted.shadowRoot.getElementById("testSvgSlottedSlot"); |
| const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); |
| const foreignObject = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject"); |
| const h1 = document.createElement("h1"); |
| foreignObject.appendChild(h1); |
| svg.appendChild(foreignObject); |
| testSvgSlotted.appendChild(svg); |
| t.add_cleanup(() => { |
| svg.remove(); |
| testSvgSlotted.removeAttribute("headingoffset"); |
| }); |
| |
| assertLevel(h1, 1, "h1 inside SVG foreignObject slotted starts at level 1"); |
| slot.parentElement.headingOffset = 4; |
| assertLevel(h1, 1, "slot wrapper headingoffset=4 does not affect slotted SVG content"); |
| testSvgSlotted.setAttribute("headingoffset", "2"); |
| assertLevel(h1, 3, "h1 becomes level 3 after host headingoffset=2"); |
| }, "non-HTML elements (SVG) slotted with heading descendants get updated via host chain"); |
| |
| test(t => { |
| const host = document.createElement("div"); |
| const shadow = host.attachShadow({ mode: "open" }); |
| const slotContainer = document.createElement("div"); |
| slotContainer.setAttribute("headingoffset", "3"); |
| const slot = document.createElement("slot"); |
| slotContainer.appendChild(slot); |
| shadow.appendChild(slotContainer); |
| |
| const mathml = document.createElementNS("http://www.w3.org/1998/Math/MathML", "math"); |
| const h1 = document.createElement("h1"); |
| mathml.appendChild(h1); |
| host.appendChild(mathml); |
| document.body.appendChild(host); |
| t.add_cleanup(() => host.remove()); |
| |
| assertLevel(h1, 1, "h1 inside MathML element slotted starts at level 1"); |
| slotContainer.headingOffset = 5; |
| assertLevel(h1, 1, "slot wrapper headingoffset does not affect slotted MathML content"); |
| host.headingOffset = 1; |
| assertLevel(h1, 2, "h1 becomes level 2 after host headingoffset=1"); |
| }, "non-HTML elements (MathML) slotted with heading descendants get updated via host chain"); |
| |
| test(t => { |
| const host = document.createElement("div"); |
| const shadow = host.attachShadow({ mode: "open", slotAssignment: "manual" }); |
| const slotContainer = document.createElement("div"); |
| slotContainer.setAttribute("headingoffset", "2"); |
| const slot = document.createElement("slot"); |
| slotContainer.appendChild(slot); |
| shadow.appendChild(slotContainer); |
| |
| const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); |
| const foreignObject = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject"); |
| const h1 = document.createElement("h1"); |
| foreignObject.appendChild(h1); |
| svg.appendChild(foreignObject); |
| host.appendChild(svg); |
| document.body.appendChild(host); |
| t.add_cleanup(() => host.remove()); |
| |
| assertLevel(h1, 1, "h1 inside SVG starts at level 1 (not manually assigned)"); |
| slot.assign(svg); |
| assertLevel(h1, 1, "h1 inside SVG stays at level 1 after SVG manually assigned"); |
| host.headingOffset = 2; |
| assertLevel(h1, 3, "h1 becomes level 3 after host headingoffset=2"); |
| slot.assign(); |
| assertLevel(h1, 3, "h1 stays at level 3 after manual slot cleared"); |
| }, "manual slot assignment of non-HTML elements does not change heading levels"); |
| </script> |