headingoffset-and-headingreset.html (12848B)
1 <!doctype html> 2 <meta charset="utf-8" /> 3 <meta name="author" title="Keith Cirkel" href="mailto:wpt@keithcirkel.co.uk" /> 4 <link rel="help" href="https://github.com/whatwg/html/pull/11086" /> 5 <script src="/resources/testharness.js"></script> 6 <script src="/resources/testharnessreport.js"></script> 7 <script src="resources/invoker-utils.js"></script> 8 9 <div headingoffset="1" title="container headingoffset=1"> 10 <!-- h1s are now h2s and so on --> 11 <h1 data-expected-offset="2"><!-- Level 2, h1 + 1 = 2 --></h1> 12 <h2 data-expected-offset="3"><!-- Level 3, h2 + 1 = 3 --></h2> 13 <h3 data-expected-offset="4"><!-- Level 4, h3 + 1 = 4 --></h3> 14 <div headingoffset="2" title="container headingoffset=2"> 15 <!-- h1s are now h4s --> 16 <h1 data-expected-offset="4"><!-- Level 4, h1 + 2 + 1 = 4 --></h1> 17 <h2 data-expected-offset="5"><!-- Level 5, h2 + 2 + 1 = 5 --></h2> 18 <div headingreset title="container headingreset"> 19 <!-- h1s are now h1s --> 20 <h1 data-expected-offset="1"><!-- Level 1, h1 (headingreset)--></h1> 21 </div> 22 <dialog open title="container dialog"> 23 <!-- non-modal dialogs do not headingreset, h1s are still h4s --> 24 <h1 data-expected-offset="4"><!-- Level 4, h1 + 2 + 1 = 4 --></h1> 25 <h1 data-expected-offset="1" headingreset> 26 <!-- Level 1, h1 (headingreset) --> 27 </h1> 28 </dialog> 29 </div> 30 </div> 31 <!-- Clamping --> 32 <div headingoffset="8" title="container headingoffset=8"> 33 <!-- h1s are now h9s --> 34 <h1 data-expected-offset="9"><!-- Level 9, h1 + 8 --></h1> 35 <h2 data-expected-offset="9"><!-- Level 9, h2 + 8 (clamped) --></h2> 36 <h3 data-expected-offset="9"><!-- Level 9, h3 + 8 (clamped) --></h3> 37 <h4 data-expected-offset="9"><!-- Level 9, h4 + 8 (clamped) --></h4> 38 <h5 data-expected-offset="9"><!-- Level 9, h5 + 8 (clamped) --></h5> 39 <h6 data-expected-offset="9"><!-- Level 9, h6 + 8 (clamped) --></h6> 40 <div headingreset title="container headingreset"> 41 <!-- h1s are now h1s --> 42 <h1 data-expected-offset="1"><!-- Level 1, h1 (headingreset)--></h1> 43 </div> 44 <dialog open title="container dialog"> 45 <!-- non-modal dialogs do not headingreset, h1s are still h4s --> 46 <h1 data-expected-offset="9"><!-- Level 9, h1 + 8 --></h1> 47 </dialog> 48 </div> 49 <!-- Negative headingoffsets are clamped to `0` --> 50 <div headingoffset="-3" title="container headingoffset=-3"> 51 <h1 data-expected-offset="1"><!-- Level 1, h1 + (-3 clamped to 0) --></h1> 52 <h2 data-expected-offset="2"><!-- Level 2, h2 + (-3 clamped to 0) --></h2> 53 <h3 data-expected-offset="3"><!-- Level 3, h3 + (-3 clamped to 0) --></h3> 54 <h4 data-expected-offset="4"><!-- Level 4, h4 + (-3 clamped to 0) --></h4> 55 <h5 data-expected-offset="5"><!-- Level 5, h5 + (-3 clamped to 0) --></h5> 56 <h6 data-expected-offset="6"><!-- Level 6, h6 + (-3 clamped to 0) --></h6> 57 <div headingreset title="container headingreset"> 58 <!-- h1s are now h1s --> 59 <h1 data-expected-offset="1"><!-- Level 1, h1 (headingreset)--></h1> 60 </div> 61 <dialog open title="container dialog"> 62 <!-- non-modal dialogs do not headingreset, h1s are still h1s --> 63 <h1 data-expected-offset="1"><!-- Level 1, h1 + (-3 clamped to 0) --></h1> 64 </dialog> 65 </div> 66 <!-- Resetting applies after headingOffset --> 67 <div headingreset title="container headingreset + headingoffset"> 68 <div headingoffset="2" headingreset> 69 <div headingoffset="2"> 70 <h1 data-expected-offset="5"><!-- Level 5, h1 + 2 + 2 = 5 --></h1> 71 <h2 data-expected-offset="6"><!-- Level 6, h2 + 2 + 2 = 6 --></h2> 72 <h3 data-expected-offset="7"><!-- Level 7, h3 + 2 + 2 = 7 --></h3> 73 <h4 data-expected-offset="8"><!-- Level 8, h4 + 2 + 2 = 8 --></h4> 74 <h5 data-expected-offset="9"><!-- Level 9, h5 + 2 + 2 = 9 --></h5> 75 <h6 data-expected-offset="9"> 76 <!-- Level 9, h6 + 2 + 2 = (10 clamped to 9) --> 77 </h6> 78 <h1 data-expected-offset="3" headingoffset="2" headingreset> 79 <!-- Level 3, h1 + 2 + (headingreset) --> 80 </h1> 81 <h2 data-expected-offset="4" headingoffset="2" headingreset> 82 <!-- Level 4, h2 + 2 + (headingreset) --> 83 </h2> 84 </div> 85 </div> 86 </div> 87 <!-- Ensure shadow roots work --> 88 <div headingoffset="1" title="container shadowroot headingoffset=1"> 89 <template shadowrootmode="open"> 90 <h1 data-expected-offset="2"><!-- Level 2, h1 + 1 --></h1> 91 <h2 data-expected-offset="2" headingreset> 92 <!-- Level 2, h2 (headingreset) --> 93 </h2> 94 </template> 95 </div> 96 <!-- Ensure slotted elements are correctly set --> 97 <div headingoffset="1" title="container shadowroot slotted headingoffset=1"> 98 <template shadowrootmode="open"> 99 <h1 data-expected-offset="2"><!-- Level 2, h1 + 1 --></h1> 100 <h2 data-expected-offset="3"><!-- Level 3, h2 + 1 --></h2> 101 <h3 data-expected-offset="3" headingreset> 102 <!-- Level 3, h3 (headingreset) --> 103 </h3> 104 <slot></slot> 105 </template> 106 <h1><!-- Level 2, h1 + 1 --></h1> 107 </div> 108 <!-- Ensure slotted elements respect their parents --> 109 <div 110 headingoffset="1" 111 title="container shadowroot slotted with container headingoffset=1" 112 > 113 <template shadowrootmode="open"> 114 <h1 data-expected-offset="2"><!-- Level 2, h1 + 1 --></h1> 115 <div headingoffset="1" title="container inside shadowroot headingoffset=1"> 116 <slot></slot> 117 </div> 118 </template> 119 <h2 data-expected-offset="4"><!-- Level 4, h2 + 1 + 1 --></h2> 120 <h4 data-expected-offset="6"><!-- Level 6, h4 + 1 + 1 --></h4> 121 <h4 data-expected-offset="4" headingreset> 122 <!-- Level 4, h4 (headingreset) --> 123 </h4> 124 </div> 125 <!-- Ensure the slot can be decorated with headingoffset --> 126 <div 127 headingoffset="1" 128 title="container shadowroot slot with attr headingoffset=1" 129 > 130 <template shadowrootmode="open"> 131 <h1 data-expected-offset="2"><!-- Level 2, h1 + 1 --></h1> 132 <slot headingoffset="1"></slot> 133 </template> 134 <h2 data-expected-offset="4"><!-- Level 4, h2 + 1 + 1 --></h2> 135 </div> 136 <h1 data-expected-offset="2" headingoffset="1"><!-- Level 2, h1 + 1 --></h1> 137 <h2 data-expected-offset="3" headingoffset="1"><!-- Level 3, h2 + 1 --></h2> 138 <h1 data-expected-offset="3" headingoffset="2"><!-- Level 3, h1 + 2--></h1> 139 <h2 data-expected-offset="4" headingoffset="2"><!-- Level 4, h2 + 2 --></h2> 140 <h1 data-expected-offset="2" headingoffset="1" headingreset> 141 <!-- Level 2, h1 + 1 (headingreset) --> 142 </h1> 143 <h2 data-expected-offset="3" headingoffset="1" headingreset> 144 <!-- Level 3, h2 + 1 (headingreset) --> 145 </h2> 146 <h1 data-expected-offset="3" headingoffset="2" headingreset> 147 <!-- Level 3, h1 + 2 (headingreset) --> 148 </h1> 149 <h2 data-expected-offset="4" headingoffset="2" headingreset> 150 <!-- Level 4, h2 + 2 (headingreset) --> 151 </h2> 152 <h1 data-expected-offset="9" headingoffset="20" headingreset> 153 <!-- Level 9, h1 + 20 (clamped) --> 154 </h1> 155 <h2 data-expected-offset="9" headingoffset="20" headingreset> 156 <!-- Level 9, h2 + 20 (clamped) --> 157 </h2> 158 <h1 data-expected-offset="1" headingoffset="0" headingreset> 159 <!-- Level 1, h1 + 0 --> 160 </h1> 161 <h2 data-expected-offset="2" headingoffset="0" headingreset> 162 <!-- Level 2, h2 + 0 --> 163 </h2> 164 <div title="container with no attr"> 165 <h1 data-expected-offset="2" headingoffset="1"><!-- Level 2, h1 + 1 --></h1> 166 <h2 data-expected-offset="3" headingoffset="1"><!-- Level 3, h2 + 1 --></h2> 167 <h1 data-expected-offset="2" headingoffset="1" headingreset> 168 <!-- Level 2, h1 + 1 --> 169 </h1> 170 <h2 data-expected-offset="3" headingoffset="1" headingreset> 171 <!-- Level 3, h2 + 1 --> 172 </h2> 173 <h1 data-expected-offset="1" headingoffset="-1" headingreset> 174 <!-- Level 1, h1 + (-1 clamped to 0) --> 175 </h1> 176 <h2 data-expected-offset="2" headingoffset="-1" headingreset> 177 <!-- Level 2, h2 + (-1 clamped to 0) --> 178 </h2> 179 </div> 180 <div headingreset title="many nested + and - values"> 181 <div headingoffset="-1"> 182 <div headingoffset="3"> 183 <div headingoffset="-6"> 184 <div headingoffset="1"> 185 <h1 data-expected-offset="5"> 186 <!-- Level 5, h1 + 1 + (-6 clamped to 0) + 3 + (-1 clamped to 0) --> 187 </h1> 188 </div> 189 </div> 190 </div> 191 </div> 192 </div> 193 <h1 data-expected-offset="9" headingoffset="9" aria-level="3"> 194 <!-- Level 3 --> 195 </h1> 196 197 <div headingoffset="9" id="modalParent"> 198 <dialog id="modal"> 199 <h1></h1> 200 </dialog> 201 </div> 202 203 <script> 204 const attribute = (el, val) => el.setAttribute("headingoffset", val); 205 const property = (el, val) => (el.headingOffset = val); 206 const levels = [1, 2, 3, 4, 5, 6, 7, 8, 9]; 207 const matchAllLevels = (el) => 208 levels.map((l) => el.matches(`:heading(${l})`)); 209 210 for (const fn of [attribute, property]) { 211 test(function () { 212 const el = document.createElement("h1"); 213 assert_true(el.matches(":heading"), `h1 should match :heading`); 214 assert_true(el.matches(":heading(1)"), `h1 should match :heading(1)`); 215 assert_equals( 216 el.headingOffset, 217 0, 218 `h1 has an initial headingOffset of 0`, 219 ); 220 fn(el, 3); 221 assert_equals(el.headingOffset, 3, `h1 now has a headingOffset of 3`); 222 assert_false( 223 el.matches(":heading(1)"), 224 `h1[headingoffset=3] should no longer match :heading(1)`, 225 ); 226 assert_true( 227 el.matches(":heading(4)"), 228 `h1[headingoffset=3] should match :heading(4)`, 229 ); 230 }, `headingoffset (set via ${fn.name}) should change the level a heading matches against`); 231 232 test(function () { 233 const parent = document.createElement("div"); 234 const el = document.createElement("h1"); 235 parent.append(el); 236 assert_true(el.matches(":heading"), `h1 should match :heading`); 237 assert_true(el.matches(":heading(1)"), `h1 should match :heading(1)`); 238 assert_equals( 239 parent.headingOffset, 240 0, 241 `parent has an initial headingOffset of 0`, 242 ); 243 fn(parent, 3); 244 assert_equals(parent.headingOffset, 3, `h1 now has a headingOffset of 3`); 245 assert_false( 246 el.matches(":heading(1)"), 247 `h1[headingoffset=3] should no longer match :heading(1)`, 248 ); 249 assert_true( 250 el.matches(":heading(4)"), 251 `div[headingoffset=3] h1 should match :heading(4)`, 252 ); 253 const bools = matchAllLevels(el); 254 const expected_bools = levels.map((l) => l == 4); 255 assert_array_equals( 256 bools, 257 expected_bools, 258 `${el.outerHTML} should match only the expected heading level`, 259 ); 260 assert_false(el.headingReset, "h1 has an initial headingReset=false"); 261 el.headingReset = true; 262 assert_true(el.headingReset, "h1 now has a headingReset of true"); 263 assert_false( 264 el.matches(":heading(4)"), 265 `div[headingoffset=3] h1[headingreset] should not match :heading(4)`, 266 ); 267 assert_true( 268 el.matches(":heading(1)"), 269 `div[headingoffset=3] h1[headingreset] should match :heading(1)`, 270 ); 271 const reset_bools = matchAllLevels(el); 272 const reset_expected_bools = levels.map((l) => l == 1); 273 assert_array_equals( 274 reset_bools, 275 reset_expected_bools, 276 `${el.outerHTML} should match only the expected heading level`, 277 ); 278 }, `headingoffset (set via ${fn.name}) should change the level when a parent changes offset`); 279 } 280 281 test(function (t) { 282 const el = modal.querySelector("h1"); 283 assert_false(modal.headingReset, `modal dialog has not set heading reset`); 284 modal.showModal(); 285 t.add_cleanup(() => modal.close()); 286 287 assert_true(modal.headingReset, `modal dialogs return headingReset true`); 288 assert_true(el.matches(":heading"), `h1 inside modal should match :heading`); 289 assert_true(el.matches(":heading(1)"), `h1 inside modal should match :heading(1)`); 290 const bools = matchAllLevels(el); 291 const expected_bools = levels.map((l) => l == 1); 292 assert_array_equals( 293 bools, 294 expected_bools, 295 `${el.outerHTML} should match only the expected heading level`, 296 ); 297 }, `headingoffset should not impact modals or explicit headingreset containers`); 298 299 let i = 0; 300 for (const el of document.querySelectorAll("[data-expected-offset]")) { 301 i += 1; 302 test(function () { 303 const expected = parseInt(el.getAttribute("data-expected-offset")); 304 assert_true( 305 expected > 0 && expected < 10, 306 "expected offset should be a level from 1 to 9", 307 ); 308 assert_true( 309 el.matches(":heading"), 310 `${el.outerHTML} should match :heading`, 311 ); 312 const bools = matchAllLevels(el); 313 const expected_bools = levels.map((l) => l == expected); 314 assert_true( 315 el.matches(`:heading(${expected})`), 316 `${el.outerHTML} should match :heading(${expected})`, 317 ); 318 assert_array_equals( 319 bools, 320 expected_bools, 321 `${el.outerHTML} should match only the expected heading level`, 322 ); 323 }, `case ${i}: heading level for ${el.outerHTML} should match based on expected document structure`); 324 } 325 </script>