Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test has a WPT meta file that expects 61 subtest issues.
- This WPT test may be referenced by the following Test IDs:
            - /html/semantics/sections/headingoffset-and-headingreset.html - WPT Dashboard Interop Dashboard
 
<!doctype html>
<meta charset="utf-8" />
<meta name="author" title="Keith Cirkel" href="mailto:wpt@keithcirkel.co.uk" />
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/invoker-utils.js"></script>
<div headingoffset="1" title="container headingoffset=1">
  <!-- h1s are now h2s and so on -->
  <h1 data-expected-offset="2"><!-- Level 2, h1 + 1 = 2 --></h1>
  <h2 data-expected-offset="3"><!-- Level 3, h2 + 1 = 3 --></h2>
  <h3 data-expected-offset="4"><!-- Level 4, h3 + 1 = 4 --></h3>
  <div headingoffset="2" title="container headingoffset=2">
    <!-- h1s are now h4s -->
    <h1 data-expected-offset="4"><!-- Level 4, h1 + 2 + 1 = 4 --></h1>
    <h2 data-expected-offset="5"><!-- Level 5, h2 + 2 + 1 = 5 --></h2>
    <div headingreset title="container headingreset">
      <!-- h1s are now h1s -->
      <h1 data-expected-offset="1"><!-- Level 1, h1 (headingreset)--></h1>
    </div>
    <dialog open title="container dialog">
      <!-- non-modal dialogs do not headingreset, h1s are still h4s -->
      <h1 data-expected-offset="4"><!-- Level 4, h1 + 2 + 1 = 4 --></h1>
      <h1 data-expected-offset="1" headingreset>
        <!-- Level 1, h1 (headingreset) -->
      </h1>
    </dialog>
  </div>
</div>
<!-- Clamping -->
<div headingoffset="8" title="container headingoffset=8">
  <!-- h1s are now h9s -->
  <h1 data-expected-offset="9"><!-- Level 9, h1 + 8 --></h1>
  <h2 data-expected-offset="9"><!-- Level 9, h2 + 8 (clamped) --></h2>
  <h3 data-expected-offset="9"><!-- Level 9, h3 + 8 (clamped) --></h3>
  <h4 data-expected-offset="9"><!-- Level 9, h4 + 8 (clamped) --></h4>
  <h5 data-expected-offset="9"><!-- Level 9, h5 + 8 (clamped) --></h5>
  <h6 data-expected-offset="9"><!-- Level 9, h6 + 8 (clamped) --></h6>
  <div headingreset title="container headingreset">
    <!-- h1s are now h1s -->
    <h1 data-expected-offset="1"><!-- Level 1, h1 (headingreset)--></h1>
  </div>
  <dialog open title="container dialog">
    <!-- non-modal dialogs do not headingreset, h1s are still h4s -->
    <h1 data-expected-offset="9"><!-- Level 9, h1 + 8 --></h1>
  </dialog>
</div>
<!-- Negative headingoffsets are clamped to `0` -->
<div headingoffset="-3" title="container headingoffset=-3">
  <h1 data-expected-offset="1"><!-- Level 1, h1 + (-3 clamped to 0) --></h1>
  <h2 data-expected-offset="2"><!-- Level 2, h2 + (-3 clamped to 0) --></h2>
  <h3 data-expected-offset="3"><!-- Level 3, h3 + (-3 clamped to 0) --></h3>
  <h4 data-expected-offset="4"><!-- Level 4, h4 + (-3 clamped to 0) --></h4>
  <h5 data-expected-offset="5"><!-- Level 5, h5 + (-3 clamped to 0) --></h5>
  <h6 data-expected-offset="6"><!-- Level 6, h6 + (-3 clamped to 0) --></h6>
  <div headingreset title="container headingreset">
    <!-- h1s are now h1s -->
    <h1 data-expected-offset="1"><!-- Level 1, h1 (headingreset)--></h1>
  </div>
  <dialog open title="container dialog">
    <!-- non-modal dialogs do not headingreset, h1s are still h1s -->
    <h1 data-expected-offset="1"><!-- Level 1, h1 + (-3 clamped to 0) --></h1>
  </dialog>
</div>
<!-- Resetting applies after headingOffset -->
<div headingreset title="container headingreset + headingoffset">
  <div headingoffset="2" headingreset>
    <div headingoffset="2">
      <h1 data-expected-offset="5"><!-- Level 5, h1 + 2 + 2 = 5 --></h1>
      <h2 data-expected-offset="6"><!-- Level 6, h2 + 2 + 2 = 6 --></h2>
      <h3 data-expected-offset="7"><!-- Level 7, h3 + 2 + 2 = 7 --></h3>
      <h4 data-expected-offset="8"><!-- Level 8, h4 + 2 + 2 = 8 --></h4>
      <h5 data-expected-offset="9"><!-- Level 9, h5 + 2 + 2 = 9 --></h5>
      <h6 data-expected-offset="9">
        <!-- Level 9, h6 + 2 + 2 = (10 clamped to 9) -->
      </h6>
      <h1 data-expected-offset="3" headingoffset="2" headingreset>
        <!-- Level 3, h1 + 2 + (headingreset) -->
      </h1>
      <h2 data-expected-offset="4" headingoffset="2" headingreset>
        <!-- Level 4, h2 + 2 + (headingreset) -->
      </h2>
    </div>
  </div>
</div>
<!-- Ensure shadow roots work -->
<div headingoffset="1" title="container shadowroot headingoffset=1">
  <template shadowrootmode="open">
    <h1 data-expected-offset="2"><!-- Level 2, h1 + 1 --></h1>
    <h2 data-expected-offset="2" headingreset>
      <!-- Level 2, h2 (headingreset) -->
    </h2>
  </template>
</div>
<!-- Ensure slotted elements are correctly set -->
<div headingoffset="1" title="container shadowroot slotted headingoffset=1">
  <template shadowrootmode="open">
    <h1 data-expected-offset="2"><!-- Level 2, h1 + 1 --></h1>
    <h2 data-expected-offset="3"><!-- Level 3, h2 + 1 --></h2>
    <h3 data-expected-offset="3" headingreset>
      <!-- Level 3, h3 (headingreset) -->
    </h3>
    <slot></slot>
  </template>
  <h1><!-- Level 2, h1 + 1 --></h1>
</div>
<!-- Ensure slotted elements respect their parents -->
<div
  headingoffset="1"
  title="container shadowroot slotted with container headingoffset=1"
>
  <template shadowrootmode="open">
    <h1 data-expected-offset="2"><!-- Level 2, h1 + 1 --></h1>
    <div headingoffset="1" title="container inside shadowroot headingoffset=1">
      <slot></slot>
    </div>
  </template>
  <h2 data-expected-offset="4"><!-- Level 4, h2 + 1 + 1 --></h2>
  <h4 data-expected-offset="6"><!-- Level 6, h4 + 1 + 1 --></h4>
  <h4 data-expected-offset="4" headingreset>
    <!-- Level 4, h4 (headingreset) -->
  </h4>
</div>
<!-- Ensure the slot can be decorated with headingoffset -->
<div
  headingoffset="1"
  title="container shadowroot slot with attr headingoffset=1"
>
  <template shadowrootmode="open">
    <h1 data-expected-offset="2"><!-- Level 2, h1 + 1 --></h1>
    <slot headingoffset="1"></slot>
  </template>
  <h2 data-expected-offset="4"><!-- Level 4, h2 + 1 + 1 --></h2>
</div>
<h1 data-expected-offset="2" headingoffset="1"><!-- Level 2, h1 + 1 --></h1>
<h2 data-expected-offset="3" headingoffset="1"><!-- Level 3, h2 + 1 --></h2>
<h1 data-expected-offset="3" headingoffset="2"><!-- Level 3, h1 + 2--></h1>
<h2 data-expected-offset="4" headingoffset="2"><!-- Level 4, h2 + 2 --></h2>
<h1 data-expected-offset="2" headingoffset="1" headingreset>
  <!-- Level 2, h1 + 1 (headingreset) -->
</h1>
<h2 data-expected-offset="3" headingoffset="1" headingreset>
  <!-- Level 3, h2 + 1 (headingreset) -->
</h2>
<h1 data-expected-offset="3" headingoffset="2" headingreset>
  <!-- Level 3, h1 + 2 (headingreset) -->
</h1>
<h2 data-expected-offset="4" headingoffset="2" headingreset>
  <!-- Level 4, h2 + 2 (headingreset) -->
</h2>
<h1 data-expected-offset="9" headingoffset="20" headingreset>
  <!-- Level 9, h1 + 20 (clamped)  -->
</h1>
<h2 data-expected-offset="9" headingoffset="20" headingreset>
  <!-- Level 9, h2 + 20 (clamped) -->
</h2>
<h1 data-expected-offset="1" headingoffset="0" headingreset>
  <!-- Level 1, h1 + 0 -->
</h1>
<h2 data-expected-offset="2" headingoffset="0" headingreset>
  <!-- Level 2, h2 + 0 -->
</h2>
<div title="container with no attr">
  <h1 data-expected-offset="2" headingoffset="1"><!-- Level 2, h1 + 1 --></h1>
  <h2 data-expected-offset="3" headingoffset="1"><!-- Level 3, h2 + 1 --></h2>
  <h1 data-expected-offset="2" headingoffset="1" headingreset>
    <!-- Level 2, h1 + 1 -->
  </h1>
  <h2 data-expected-offset="3" headingoffset="1" headingreset>
    <!-- Level 3, h2 + 1 -->
  </h2>
  <h1 data-expected-offset="1" headingoffset="-1" headingreset>
    <!-- Level 1, h1 + (-1 clamped to 0) -->
  </h1>
  <h2 data-expected-offset="2" headingoffset="-1" headingreset>
    <!-- Level 2, h2 + (-1 clamped to 0) -->
  </h2>
</div>
<div headingreset title="many nested + and - values">
  <div headingoffset="-1">
    <div headingoffset="3">
      <div headingoffset="-6">
        <div headingoffset="1">
          <h1 data-expected-offset="5">
            <!-- Level 5, h1 + 1 + (-6 clamped to 0) + 3 + (-1 clamped to 0) -->
          </h1>
        </div>
      </div>
    </div>
  </div>
</div>
<h1 data-expected-offset="9" headingoffset="9" aria-level="3">
  <!-- Level 3 -->
</h1>
<div headingoffset="9" id="modalParent">
  <dialog id="modal">
    <h1></h1>
  </dialog>
</div>
<script>
  const attribute = (el, val) => el.setAttribute("headingoffset", val);
  const property = (el, val) => (el.headingOffset = val);
  const levels = [1, 2, 3, 4, 5, 6, 7, 8, 9];
  const matchAllLevels = (el) =>
    levels.map((l) => el.matches(`:heading(${l})`));
  for (const fn of [attribute, property]) {
    test(function () {
      const el = document.createElement("h1");
      assert_true(el.matches(":heading"), `h1 should match :heading`);
      assert_true(el.matches(":heading(1)"), `h1 should match :heading(1)`);
      assert_equals(
        el.headingOffset,
        0,
        `h1 has an initial headingOffset of 0`,
      );
      fn(el, 3);
      assert_equals(el.headingOffset, 3, `h1 now has a headingOffset of 3`);
      assert_false(
        el.matches(":heading(1)"),
        `h1[headingoffset=3] should no longer match :heading(1)`,
      );
      assert_true(
        el.matches(":heading(4)"),
        `h1[headingoffset=3] should match :heading(4)`,
      );
    }, `headingoffset (set via ${fn.name}) should change the level a heading matches against`);
    test(function () {
      const parent = document.createElement("div");
      const el = document.createElement("h1");
      parent.append(el);
      assert_true(el.matches(":heading"), `h1 should match :heading`);
      assert_true(el.matches(":heading(1)"), `h1 should match :heading(1)`);
      assert_equals(
        parent.headingOffset,
        0,
        `parent has an initial headingOffset of 0`,
      );
      fn(parent, 3);
      assert_equals(parent.headingOffset, 3, `h1 now has a headingOffset of 3`);
      assert_false(
        el.matches(":heading(1)"),
        `h1[headingoffset=3] should no longer match :heading(1)`,
      );
      assert_true(
        el.matches(":heading(4)"),
        `div[headingoffset=3] h1 should match :heading(4)`,
      );
      const bools = matchAllLevels(el);
      const expected_bools = levels.map((l) => l == 4);
      assert_array_equals(
        bools,
        expected_bools,
        `${el.outerHTML} should match only the expected heading level`,
      );
      assert_false(el.headingReset, "h1 has an initial headingReset=false");
      el.headingReset = true;
      assert_true(el.headingReset, "h1 now has a headingReset of true");
      assert_false(
        el.matches(":heading(4)"),
        `div[headingoffset=3] h1[headingreset] should not match :heading(4)`,
      );
      assert_true(
        el.matches(":heading(1)"),
        `div[headingoffset=3] h1[headingreset] should match :heading(1)`,
      );
      const reset_bools = matchAllLevels(el);
      const reset_expected_bools = levels.map((l) => l == 1);
      assert_array_equals(
        reset_bools,
        reset_expected_bools,
        `${el.outerHTML} should match only the expected heading level`,
      );
    }, `headingoffset (set via ${fn.name}) should change the level when a parent changes offset`);
  }
  test(function (t) {
    const el = modal.querySelector("h1");
    assert_false(modal.headingReset, `modal dialog has not set heading reset`);
    modal.showModal();
    t.add_cleanup(() => modal.close());
    assert_true(modal.headingReset, `modal dialogs return headingReset true`);
    assert_true(el.matches(":heading"), `h1 inside modal should match :heading`);
    assert_true(el.matches(":heading(1)"), `h1 inside modal should match :heading(1)`);
    const bools = matchAllLevels(el);
    const expected_bools = levels.map((l) => l == 1);
    assert_array_equals(
      bools,
      expected_bools,
      `${el.outerHTML} should match only the expected heading level`,
    );
  }, `headingoffset should not impact modals or explicit headingreset containers`);
  let i = 0;
  for (const el of document.querySelectorAll("[data-expected-offset]")) {
    i += 1;
    test(function () {
      const expected = parseInt(el.getAttribute("data-expected-offset"));
      assert_true(
        expected > 0 && expected < 10,
        "expected offset should be a level from 1 to 9",
      );
      assert_true(
        el.matches(":heading"),
        `${el.outerHTML} should match :heading`,
      );
      const bools = matchAllLevels(el);
      const expected_bools = levels.map((l) => l == expected);
      assert_true(
        el.matches(`:heading(${expected})`),
        `${el.outerHTML} should match :heading(${expected})`,
      );
      assert_array_equals(
        bools,
        expected_bools,
        `${el.outerHTML} should match only the expected heading level`,
      );
    }, `case ${i}: heading level for ${el.outerHTML} should match based on expected document structure`);
  }
</script>