| <!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 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_false(modal.headingReset, `modal dialogs return headingReset false by default`); |
| assert_true(el.matches(":heading"), `h1 inside modal should match :heading`); |
| assert_true(el.matches(":heading(9)"), `h1 inside modal should match :heading(9)`); |
| modal.headingReset = true; |
| assert_true(el.matches(":heading(1)"), `h1 inside modal should match :heading(1) if modal is headingreset`); |
| 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> |