| <!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, 3, "slotted h1 becomes level 3 after slot headingoffset=2"); |
| set(testSlotted, 1); |
| assertLevel(testSlottedH1, 4, "slotted h1 becomes level 4 (1+2+1)"); |
| }, `headingoffset ${style} mutations on slot element 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, 3, "h1 in nested shadow becomes level 3 after slot headingoffset=2"); |
| set(testSlottedNested, 1); |
| assertLevel(h1, 4, "h1 in nested shadow becomes level 4 (1+2+1)"); |
| }, `headingoffset ${style} mutations on slot traverse into slotted element 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, 7, "h1 in nested shadow starts at level 7 (1+2+3+1)"); |
| set(testNestedShadow, 0); |
| assertLevel(h1, 6, "h1 becomes level 6 after outermost headingoffset=0 (0+2+3+1)"); |
| set(innerDiv, 0); |
| assertLevel(h1, 4, "h1 becomes level 4 after inner shadow div headingoffset=0 (0+0+3+1)"); |
| set(innerShadowDiv, 0); |
| assertLevel(h1, 1, "h1 becomes level 1 after all headingoffsets cleared"); |
| set(testNestedShadow, 2); |
| assertLevel(h1, 3, "h1 becomes level 3 after outermost headingoffset=2 (2+0+0+1)"); |
| }, `headingoffset ${style} mutations propagate through two levels of shadow DOM with slotting`); |
| |
| 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); |
| }); |
| |
| assertLevel(h1, 3, "h1 in triple shadow starts at level 3 (1+1+1)"); |
| set(l1, 3); |
| assertLevel(h1, 5, "h1 becomes level 5 after l1 headingoffset=3 (3+1+1)"); |
| set(l2, 2); |
| assertLevel(h1, 6, "h1 becomes level 6 after l2 headingoffset=2 (3+2+1)"); |
| clear(l1); |
| assertLevel(h1, 3, "h1 becomes level 3 after l1 cleared (0+2+1)"); |
| }, `headingoffset ${style} mutations propagate through three levels of nested shadow DOM`); |
| |
| 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, 4, "child h1 starts at level 4 (1+2+1)"); |
| set(l1, 3); |
| assertLevel(innerH1, 4, "inner h1 becomes level 4 after l1 headingoffset=3 (3+1)"); |
| assertLevel(childH1, 6, "child h1 becomes level 6 after l1 headingoffset=3 (3+2+1)"); |
| set(l2, 0); |
| assertLevel(innerH1, 4, "inner h1 stays at level 4 (l2 change doesn't affect it)"); |
| assertLevel(childH1, 4, "child h1 becomes level 4 after l2 headingoffset=0 (3+0+1)"); |
| }, `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"; |
| }); |
| |
| assertLevel(testSlotNameChangeH1, 3, "h1 slotted into slot a starts at level 3 (2+1)"); |
| slotA.name = "c"; |
| assertLevel(testSlotNameChangeH1, 1, "h1 becomes level 1 when slot name changes and h1 no longer slotted"); |
| slotB.name = "a"; |
| assertLevel(testSlotNameChangeH1, 5, "h1 becomes level 5 when reslotted into slot b (4+1)"); |
| }, "slot name attribute changes update slotted heading levels"); |
| |
| test(t => { |
| t.add_cleanup(() => { |
| testSlotAttrChangeH1.slot = "x"; |
| }); |
| |
| assertLevel(testSlotAttrChangeH1, 2, "h1 slotted into slot x starts at level 2 (1+1)"); |
| testSlotAttrChangeH1.slot = "y"; |
| assertLevel(testSlotAttrChangeH1, 4, "h1 becomes level 4 when moved to slot y (3+1)"); |
| testSlotAttrChangeH1.slot = "nonexistent"; |
| assertLevel(testSlotAttrChangeH1, 1, "h1 becomes level 1 when slot attribute points to nonexistent slot"); |
| testSlotAttrChangeH1.slot = "x"; |
| assertLevel(testSlotAttrChangeH1, 2, "h1 returns to level 2 when moved back to slot x"); |
| }, "element slot attribute changes update heading levels"); |
| |
| test(t => { |
| const slot = testSlotRemoval.shadowRoot.getElementById("testSlotRemovalSlot"); |
| const container = slot.parentElement; |
| t.add_cleanup(() => { |
| container.appendChild(slot); |
| }); |
| |
| assertLevel(testSlotRemovalH1, 6, "h1 slotted into default slot starts at level 6 (5+1)"); |
| slot.remove(); |
| assertLevel(testSlotRemovalH1, 1, "h1 becomes level 1 when slot element is removed"); |
| container.appendChild(slot); |
| assertLevel(testSlotRemovalH1, 6, "h1 returns to level 6 when slot is re-added"); |
| }, "slot element removal and addition updates slotted heading levels"); |
| |
| test(t => { |
| const slot = testSlotDefault.shadowRoot.getElementById("testSlotDefaultSlot"); |
| t.add_cleanup(() => { |
| slot.removeAttribute("name"); |
| testSlotDefaultH1.removeAttribute("slot"); |
| }); |
| |
| assertLevel(testSlotDefaultH1, 3, "h1 in default slot starts at level 3 (2+1)"); |
| slot.name = "named"; |
| assertLevel(testSlotDefaultH1, 1, "h1 becomes level 1 when default slot becomes named"); |
| testSlotDefaultH1.slot = "named"; |
| assertLevel(testSlotDefaultH1, 3, "h1 returns to level 3 when explicitly assigned to named slot"); |
| slot.removeAttribute("name"); |
| assertLevel(testSlotDefaultH1, 1, "h1 becomes level 1 when slot becomes default but h1 has explicit slot attr"); |
| testSlotDefaultH1.removeAttribute("slot"); |
| assertLevel(testSlotDefaultH1, 3, "h1 returns to level 3 when slot attr removed and falls back to default"); |
| }, "default slot to named slot transitions update 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"; |
| }); |
| |
| assertLevel(testSlotMultipleH1A, 2, "h1 A in slot first starts at level 2 (1+1)"); |
| assertLevel(testSlotMultipleH1B, 4, "h1 B in slot second starts at level 4 (3+1)"); |
| |
| slotFirst.name = "second"; |
| slotSecond.name = "first"; |
| |
| assertLevel(testSlotMultipleH1A, 4, "h1 A becomes level 4 after slot swap (now in second's container)"); |
| assertLevel(testSlotMultipleH1B, 2, "h1 B becomes level 2 after slot swap (now in first's container)"); |
| }, "swapping slot names updates multiple headings correctly"); |
| |
| 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(); |
| }); |
| |
| assertLevel(h1, 1, "h1 starts at level 1 (not manually assigned)"); |
| assertLevel(divH1, 1, "h1 inside div starts at level 1 (not manually assigned)"); |
| |
| slot.assign(h1); |
| assertLevel(h1, 3, "h1 becomes level 3 after manual slot assignment (2+1)"); |
| |
| slot.assign(div); |
| assertLevel(h1, 1, "h1 returns to level 1 after unassignment"); |
| assertLevel(divH1, 3, "h1 inside div becomes level 3 after div manually assigned (2+1)"); |
| |
| slot.assign(); |
| assertLevel(divH1, 1, "h1 inside div returns to level 1 after slot cleared"); |
| }, "manual slot assignment updates 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(); |
| }); |
| |
| assertLevel(h1, 1, "h1 starts at level 1 (not assigned)"); |
| slotA.assign(h1); |
| assertLevel(h1, 2, "h1 becomes level 2 after assigned to slot A (1+1)"); |
| slotB.assign(h1); |
| assertLevel(h1, 4, "h1 becomes level 4 after reassigned to slot B (3+1)"); |
| slotA.assign(h1); |
| assertLevel(h1, 2, "h1 becomes level 2 after reassigned back to slot A (1+1)"); |
| }, "manual slot reassignment between slots updates 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()); |
| |
| assertLevel(h1, 3, "h1 inside SVG foreignObject slotted starts at level 3 (2+1)"); |
| slot.parentElement.headingOffset = 4; |
| assertLevel(h1, 5, "h1 inside SVG foreignObject becomes level 5 after headingoffset=4 (4+1)"); |
| slot.parentElement.headingOffset = 2; |
| assertLevel(h1, 3, "h1 inside SVG foreignObject returns to level 3 (2+1)"); |
| }, "non-HTML elements (SVG) slotted with heading descendants get updated"); |
| |
| 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, 4, "h1 inside MathML element slotted starts at level 4 (3+1)"); |
| slotContainer.headingOffset = 1; |
| assertLevel(h1, 2, "h1 inside MathML element becomes level 2 after headingoffset=1 (1+1)"); |
| }, "non-HTML elements (MathML) slotted with heading descendants get updated"); |
| |
| 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, 3, "h1 inside SVG becomes level 3 after SVG manually assigned (2+1)"); |
| slot.assign(); |
| assertLevel(h1, 1, "h1 inside SVG returns to level 1 after manual slot cleared"); |
| }, "manual slot assignment of non-HTML elements updates heading descendants"); |
| </script> |