Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

<!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>