Element-classlist.html (17163B)
1 <!doctype html> 2 <meta charset=utf-8> 3 <title>Test for the classList element attribute</title> 4 <script src=/resources/testharness.js></script> 5 <script src=/resources/testharnessreport.js></script> 6 <div id="content"></div> 7 <script> 8 const SVG_NS = "http://www.w3.org/2000/svg"; 9 const XHTML_NS = "http://www.w3.org/1999/xhtml" 10 const MATHML_NS = "http://www.w3.org/1998/Math/MathML"; 11 12 function setClass(e, newVal) { 13 if (newVal === null) { 14 e.removeAttribute("class"); 15 } else { 16 e.setAttribute("class", newVal); 17 } 18 } 19 20 function checkModification(e, funcName, args, expectedRes, before, after, 21 expectedException, desc) { 22 if (!Array.isArray(args)) { 23 args = [args]; 24 } 25 26 test(function() { 27 var shouldThrow = typeof(expectedException) === "string"; 28 if (shouldThrow) { 29 // If an exception is thrown, the class attribute shouldn't change. 30 after = before; 31 } 32 setClass(e, before); 33 34 var obs; 35 // If we have MutationObservers available, do some checks to make 36 // sure attribute sets are happening at sane times. 37 if (self.MutationObserver) { 38 obs = new MutationObserver(() => {}); 39 obs.observe(e, { attributes: true }); 40 } 41 if (shouldThrow) { 42 assert_throws_dom(expectedException, function() { 43 var list = e.classList; 44 var res = list[funcName].apply(list, args); 45 }); 46 } else { 47 var list = e.classList; 48 var res = list[funcName].apply(list, args); 49 } 50 if (obs) { 51 var mutationRecords = obs.takeRecords(); 52 obs.disconnect(); 53 if (shouldThrow) { 54 assert_equals(mutationRecords.length, 0, 55 "There should have been no mutation"); 56 } else if (funcName == "replace") { 57 assert_equals(mutationRecords.length == 1, 58 expectedRes, 59 "Should have a mutation exactly when replace() returns true"); 60 } else { 61 // For other functions, would need to check when exactly 62 // mutations are supposed to happen. 63 } 64 } 65 if (!shouldThrow) { 66 assert_equals(res, expectedRes, "wrong return value"); 67 } 68 69 var expectedAfter = after; 70 71 assert_equals(e.getAttribute("class"), expectedAfter, 72 "wrong class after modification"); 73 }, "classList." + funcName + "(" + args.map(format_value).join(", ") + 74 ") with attribute value " + format_value(before) + desc); 75 } 76 77 function assignToClassListStrict(e) { 78 "use strict"; 79 e.classList = "foo"; 80 e.removeAttribute("class"); 81 } 82 83 function assignToClassList(e) { 84 var expect = e.classList; 85 e.classList = "foo"; 86 assert_equals(e.classList, expect, 87 "classList should be unchanged after assignment"); 88 e.removeAttribute("class"); 89 } 90 91 function testClassList(e, desc) { 92 93 // assignment 94 95 test(function() { 96 assignToClassListStrict(e); 97 assignToClassList(e); 98 }, "Assigning to classList" + desc); 99 100 // supports 101 test(function() { 102 assert_throws_js(TypeError, function() { 103 e.classList.supports("a"); 104 }) 105 }, ".supports() must throw TypeError" + desc); 106 107 // length attribute 108 109 function checkLength(value, length) { 110 test(function() { 111 setClass(e, value); 112 assert_equals(e.classList.length, length); 113 }, "classList.length when " + 114 (value === null ? "removed" : "set to " + format_value(value)) + desc); 115 } 116 117 checkLength(null, 0); 118 checkLength("", 0); 119 checkLength(" \t \f", 0); 120 checkLength("a", 1); 121 checkLength("a A", 2); 122 checkLength("\r\na\t\f", 1); 123 checkLength("a a", 1); 124 checkLength("a a a a a a", 1); 125 checkLength("a a b b", 2); 126 checkLength("a A B b", 4); 127 checkLength("a b c c b a a b c c", 3); 128 checkLength(" a a b", 2); 129 checkLength("a\tb\nc\fd\re f", 6); 130 131 // [Stringifies] 132 133 function checkStringifier(value, expected) { 134 test(function() { 135 setClass(e, value); 136 assert_equals(e.classList.toString(), expected); 137 }, "classList.toString() when " + 138 (value === null ? "removed" : "set to " + format_value(value)) + desc); 139 } 140 141 checkStringifier(null, ""); 142 checkStringifier("foo", "foo"); 143 checkStringifier(" a a b", " a a b"); 144 145 // item() method 146 147 function checkItems(attributeValue, expectedValues) { 148 function checkItemFunction(index, expected) { 149 assert_equals(e.classList.item(index), expected, 150 "classList.item(" + index + ")"); 151 } 152 153 function checkItemArray(index, expected) { 154 assert_equals(e.classList[index], expected, "classList[" + index + "]"); 155 } 156 157 test(function() { 158 setClass(e, attributeValue); 159 160 checkItemFunction(-1, null); 161 checkItemArray(-1, undefined); 162 163 var i = 0; 164 while (i < expectedValues.length) { 165 checkItemFunction(i, expectedValues[i]); 166 checkItemArray(i, expectedValues[i]); 167 i++; 168 } 169 170 checkItemFunction(i, null); 171 checkItemArray(i, undefined); 172 173 checkItemFunction(0xffffffff, null); 174 checkItemArray(0xffffffff, undefined); 175 176 checkItemFunction(0xfffffffe, null); 177 checkItemArray(0xfffffffe, undefined); 178 }, "classList.item() when set to " + format_value(attributeValue) + desc); 179 } 180 181 checkItems(null, []); 182 checkItems("a", ["a"]); 183 checkItems("aa AA aa", ["aa", "AA"]); 184 checkItems("a b", ["a", "b"]); 185 checkItems(" a a b", ["a", "b"]); 186 checkItems("\t\n\f\r a\t\n\f\r b\t\n\f\r ", ["a", "b"]); 187 188 // contains() method 189 190 function checkContains(attributeValue, args, expectedRes) { 191 if (!Array.isArray(expectedRes)) { 192 expectedRes = Array(args.length).fill(expectedRes); 193 } 194 setClass(e, attributeValue); 195 for (var i = 0; i < args.length; i++) { 196 test(function() { 197 assert_equals(e.classList.contains(args[i]), expectedRes[i], 198 "classList.contains(\"" + args[i] + "\")"); 199 }, "classList.contains(" + format_value(args[i]) + ") when set to " + 200 format_value(attributeValue) + desc); 201 } 202 } 203 204 checkContains(null, ["a", "", " "], false); 205 checkContains("", ["a"], false); 206 207 checkContains("a", ["a"], true); 208 checkContains("a", ["aa", "b", "A", "a.", "a)",, "a'", 'a"', "a$", "a~", 209 "a?", "a\\"], false); 210 211 // All "ASCII whitespace" per spec, before and after 212 checkContains("a", ["a\t", "\ta", "a\n", "\na", "a\f", "\fa", "a\r", "\ra", 213 "a ", " a"], false); 214 215 checkContains("aa AA", ["aa", "AA", "aA"], [true, true, false]); 216 checkContains("a a a", ["a", "aa", "b"], [true, false, false]); 217 checkContains("a b c", ["a", "b"], true); 218 219 checkContains("null undefined", [null, undefined], true); 220 checkContains("\t\n\f\r a\t\n\f\r b\t\n\f\r ", ["a", "b"], true); 221 222 // add() method 223 224 function checkAdd(before, argument, after, param) { 225 var expectedException = undefined; 226 var noop = false; 227 if (param == "noop") { 228 noop = true; 229 } else { 230 expectedException = param; 231 } 232 checkModification(e, "add", argument, undefined, before, after, 233 expectedException, desc); 234 // Also check force toggle. The only difference is that it doesn't run the 235 // update steps for a no-op. 236 if (!Array.isArray(argument)) { 237 checkModification(e, "toggle", [argument, true], true, before, 238 noop ? before : after, expectedException, desc); 239 } 240 } 241 242 checkAdd(null, "", null, "SyntaxError"); 243 checkAdd(null, ["a", ""], null, "SyntaxError"); 244 checkAdd(null, " ", null, "InvalidCharacterError"); 245 checkAdd(null, "\ta", null, "InvalidCharacterError"); 246 checkAdd(null, "a\t", null, "InvalidCharacterError"); 247 checkAdd(null, "\na", null, "InvalidCharacterError"); 248 checkAdd(null, "a\n", null, "InvalidCharacterError"); 249 checkAdd(null, "\fa", null, "InvalidCharacterError"); 250 checkAdd(null, "a\f", null, "InvalidCharacterError"); 251 checkAdd(null, "\ra", null, "InvalidCharacterError"); 252 checkAdd(null, "a\r", null, "InvalidCharacterError"); 253 checkAdd(null, " a", null, "InvalidCharacterError"); 254 checkAdd(null, "a ", null, "InvalidCharacterError"); 255 checkAdd(null, ["a", " "], null, "InvalidCharacterError"); 256 checkAdd(null, ["a", "aa "], null, "InvalidCharacterError"); 257 258 checkAdd("a", "a", "a"); 259 checkAdd("aa", "AA", "aa AA"); 260 checkAdd("a b c", "a", "a b c"); 261 checkAdd("a a a b", "a", "a b", "noop"); 262 checkAdd(null, "a", "a"); 263 checkAdd("", "a", "a"); 264 checkAdd(" ", "a", "a"); 265 checkAdd(" \f", "a", "a"); 266 checkAdd("a", "b", "a b"); 267 checkAdd("a b c", "d", "a b c d"); 268 checkAdd("a b c ", "d", "a b c d"); 269 checkAdd(" a a b", "c", "a b c"); 270 checkAdd(" a a b", "a", "a b", "noop"); 271 checkAdd("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "c", "a b c"); 272 273 // multiple add 274 checkAdd("a b c ", ["d", "e"], "a b c d e"); 275 checkAdd("a b c ", ["a", "a"], "a b c"); 276 checkAdd("a b c ", ["d", "d"], "a b c d"); 277 checkAdd("a b c a ", [], "a b c"); 278 checkAdd(null, ["a", "b"], "a b"); 279 checkAdd("", ["a", "b"], "a b"); 280 281 checkAdd(null, null, "null"); 282 checkAdd(null, undefined, "undefined"); 283 284 // remove() method 285 286 function checkRemove(before, argument, after, param) { 287 var expectedException = undefined; 288 var noop = false; 289 if (param == "noop") { 290 noop = true; 291 } else { 292 expectedException = param; 293 } 294 checkModification(e, "remove", argument, undefined, before, after, 295 expectedException, desc); 296 // Also check force toggle. The only difference is that it doesn't run the 297 // update steps for a no-op. 298 if (!Array.isArray(argument)) { 299 checkModification(e, "toggle", [argument, false], false, before, 300 noop ? before : after, expectedException, desc); 301 } 302 } 303 304 checkRemove(null, "", null, "SyntaxError"); 305 checkRemove(null, " ", null, "InvalidCharacterError"); 306 checkRemove("\ta", "\ta", "\ta", "InvalidCharacterError"); 307 checkRemove("a\t", "a\t", "a\t", "InvalidCharacterError"); 308 checkRemove("\na", "\na", "\na", "InvalidCharacterError"); 309 checkRemove("a\n", "a\n", "a\n", "InvalidCharacterError"); 310 checkRemove("\fa", "\fa", "\fa", "InvalidCharacterError"); 311 checkRemove("a\f", "a\f", "a\f", "InvalidCharacterError"); 312 checkRemove("\ra", "\ra", "\ra", "InvalidCharacterError"); 313 checkRemove("a\r", "a\r", "a\r", "InvalidCharacterError"); 314 checkRemove(" a", " a", " a", "InvalidCharacterError"); 315 checkRemove("a ", "a ", "a ", "InvalidCharacterError"); 316 checkRemove("aa ", "aa ", null, "InvalidCharacterError"); 317 318 checkRemove(null, "a", null); 319 checkRemove("", "a", ""); 320 checkRemove("a b c", "d", "a b c", "noop"); 321 checkRemove("a b c", "A", "a b c", "noop"); 322 checkRemove(" a a a ", "a", ""); 323 checkRemove("a b", "a", "b"); 324 checkRemove("a b ", "a", "b"); 325 checkRemove("a a b", "a", "b"); 326 checkRemove("aa aa bb", "aa", "bb"); 327 checkRemove("a a b a a c a a", "a", "b c"); 328 329 checkRemove("a b c", "b", "a c"); 330 checkRemove("aaa bbb ccc", "bbb", "aaa ccc"); 331 checkRemove(" a b c ", "b", "a c"); 332 checkRemove("a b b b c", "b", "a c"); 333 334 checkRemove("a b c", "c", "a b"); 335 checkRemove(" a b c ", "c", "a b"); 336 checkRemove("a b c c c", "c", "a b"); 337 338 checkRemove("a b a c a d a", "a", "b c d"); 339 checkRemove("AA BB aa CC AA dd aa", "AA", "BB aa CC dd"); 340 341 checkRemove("\ra\na\ta\f", "a", ""); 342 checkRemove("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "a", "b"); 343 344 // multiple remove 345 checkRemove("a b c ", ["d", "e"], "a b c"); 346 checkRemove("a b c ", ["a", "b"], "c"); 347 checkRemove("a b c ", ["a", "c"], "b"); 348 checkRemove("a b c ", ["a", "a"], "b c"); 349 checkRemove("a b c ", ["d", "d"], "a b c"); 350 checkRemove("a b c ", [], "a b c"); 351 checkRemove(null, ["a", "b"], null); 352 checkRemove("", ["a", "b"], ""); 353 checkRemove("a a", [], "a"); 354 355 checkRemove("null", null, ""); 356 checkRemove("undefined", undefined, ""); 357 358 // toggle() method 359 360 function checkToggle(before, argument, expectedRes, after, expectedException) { 361 checkModification(e, "toggle", argument, expectedRes, before, after, 362 expectedException, desc); 363 } 364 365 checkToggle(null, "", null, null, "SyntaxError"); 366 checkToggle(null, "aa ", null, null, "InvalidCharacterError"); 367 368 checkToggle(null, "a", true, "a"); 369 checkToggle("", "a", true, "a"); 370 checkToggle(" ", "a", true, "a"); 371 checkToggle(" \f", "a", true, "a"); 372 checkToggle("a", "b", true, "a b"); 373 checkToggle("a", "A", true, "a A"); 374 checkToggle("a b c", "d", true, "a b c d"); 375 checkToggle(" a a b", "d", true, "a b d"); 376 377 checkToggle("a", "a", false, ""); 378 checkToggle(" a a a ", "a", false, ""); 379 checkToggle(" A A A ", "a", true, "A a"); 380 checkToggle(" a b c ", "b", false, "a c"); 381 checkToggle(" a b c b b", "b", false, "a c"); 382 checkToggle(" a b c ", "c", false, "a b"); 383 checkToggle(" a b c ", "a", false, "b c"); 384 checkToggle(" a a b", "b", false, "a"); 385 checkToggle("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "a", false, "b"); 386 checkToggle("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "c", true, "a b c"); 387 388 checkToggle("null", null, false, ""); 389 checkToggle("", null, true, "null"); 390 checkToggle("undefined", undefined, false, ""); 391 checkToggle("", undefined, true, "undefined"); 392 393 394 // replace() method 395 function checkReplace(before, token, newToken, expectedRes, after, expectedException) { 396 checkModification(e, "replace", [token, newToken], expectedRes, before, 397 after, expectedException, desc); 398 } 399 400 checkReplace(null, "", "a", null, null, "SyntaxError"); 401 checkReplace(null, "", " ", null, null, "SyntaxError"); 402 checkReplace(null, " ", "a", null, null, "InvalidCharacterError"); 403 checkReplace(null, "\ta", "b", null, null, "InvalidCharacterError"); 404 checkReplace(null, "a\t", "b", null, null, "InvalidCharacterError"); 405 checkReplace(null, "\na", "b", null, null, "InvalidCharacterError"); 406 checkReplace(null, "a\n", "b", null, null, "InvalidCharacterError"); 407 checkReplace(null, "\fa", "b", null, null, "InvalidCharacterError"); 408 checkReplace(null, "a\f", "b", null, null, "InvalidCharacterError"); 409 checkReplace(null, "\ra", "b", null, null, "InvalidCharacterError"); 410 checkReplace(null, "a\r", "b", null, null, "InvalidCharacterError"); 411 checkReplace(null, " a", "b", null, null, "InvalidCharacterError"); 412 checkReplace(null, "a ", "b", null, null, "InvalidCharacterError"); 413 414 checkReplace(null, "a", "", null, null, "SyntaxError"); 415 checkReplace(null, " ", "", null, null, "SyntaxError"); 416 checkReplace(null, "a", " ", null, null, "InvalidCharacterError"); 417 checkReplace(null, "b", "\ta", null, null, "InvalidCharacterError"); 418 checkReplace(null, "b", "a\t", null, null, "InvalidCharacterError"); 419 checkReplace(null, "b", "\na", null, null, "InvalidCharacterError"); 420 checkReplace(null, "b", "a\n", null, null, "InvalidCharacterError"); 421 checkReplace(null, "b", "\fa", null, null, "InvalidCharacterError"); 422 checkReplace(null, "b", "a\f", null, null, "InvalidCharacterError"); 423 checkReplace(null, "b", "\ra", null, null, "InvalidCharacterError"); 424 checkReplace(null, "b", "a\r", null, null, "InvalidCharacterError"); 425 checkReplace(null, "b", " a", null, null, "InvalidCharacterError"); 426 checkReplace(null, "b", "a ", null, null, "InvalidCharacterError"); 427 428 checkReplace("a", "a", "a", true, "a"); 429 checkReplace("a", "a", "b", true, "b"); 430 checkReplace("a", "A", "b", false, "a"); 431 checkReplace("a b", "b", "A", true, "a A"); 432 checkReplace("a b", "c", "a", false, "a b"); 433 checkReplace("a b c", "d", "e", false, "a b c"); 434 // https://github.com/whatwg/dom/issues/443 435 checkReplace("a a a b", "a", "a", true, "a b"); 436 checkReplace("a a a b", "c", "d", false, "a a a b"); 437 checkReplace(null, "a", "b", false, null); 438 checkReplace("", "a", "b", false, ""); 439 checkReplace(" ", "a", "b", false, " "); 440 checkReplace(" a \f", "a", "b", true, "b"); 441 checkReplace("a b c", "b", "d", true, "a d c"); 442 checkReplace("a b c", "c", "a", true, "a b"); 443 checkReplace("c b a", "c", "a", true, "a b"); 444 checkReplace("a b a", "a", "c", true, "c b"); 445 checkReplace("a b a", "b", "c", true, "a c"); 446 checkReplace(" a a b", "a", "c", true, "c b"); 447 checkReplace(" a a b", "b", "c", true, "a c"); 448 checkReplace("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "a", "c", true, "c b"); 449 checkReplace("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "b", "c", true, "a c"); 450 451 checkReplace("a null", null, "b", true, "a b"); 452 checkReplace("a b", "a", null, true, "null b"); 453 checkReplace("a undefined", undefined, "b", true, "a b"); 454 checkReplace("a b", "a", undefined, true, "undefined b"); 455 } 456 457 var content = document.getElementById("content"); 458 459 var htmlNode = document.createElement("div"); 460 content.appendChild(htmlNode); 461 testClassList(htmlNode, " (HTML node)"); 462 463 var xhtmlNode = document.createElementNS(XHTML_NS, "div"); 464 content.appendChild(xhtmlNode); 465 testClassList(xhtmlNode, " (XHTML node)"); 466 467 var mathMLNode = document.createElementNS(MATHML_NS, "math"); 468 content.appendChild(mathMLNode); 469 testClassList(mathMLNode, " (MathML node)"); 470 471 var xmlNode = document.createElementNS(null, "foo"); 472 content.appendChild(xmlNode); 473 testClassList(xmlNode, " (XML node with null namespace)"); 474 475 var fooNode = document.createElementNS("http://example.org/foo", "foo"); 476 content.appendChild(fooNode); 477 testClassList(fooNode, " (foo node)"); 478 </script>