| <!doctype html> |
| <meta charset=utf-8> |
| <title>Test for the classList element attribute</title> |
| <script src=/resources/testharness.js></script> |
| <script src=/resources/testharnessreport.js></script> |
| <div id="content"></div> |
| <script> |
| const SVG_NS = "http://www.w3.org/2000/svg"; |
| const XHTML_NS = "http://www.w3.org/1999/xhtml" |
| const MATHML_NS = "http://www.w3.org/1998/Math/MathML"; |
| |
| function setClass(e, newVal) { |
| if (newVal === null) { |
| e.removeAttribute("class"); |
| } else { |
| e.setAttribute("class", newVal); |
| } |
| } |
| |
| function checkModification(e, funcName, args, expectedRes, before, after, |
| expectedException, desc) { |
| if (!Array.isArray(args)) { |
| args = [args]; |
| } |
| |
| test(function() { |
| var shouldThrow = typeof(expectedException) === "string"; |
| if (shouldThrow) { |
| // If an exception is thrown, the class attribute shouldn't change. |
| after = before; |
| } |
| setClass(e, before); |
| |
| var obs; |
| // If we have MutationObservers available, do some checks to make |
| // sure attribute sets are happening at sane times. |
| if (self.MutationObserver) { |
| obs = new MutationObserver(() => {}); |
| obs.observe(e, { attributes: true }); |
| } |
| if (shouldThrow) { |
| assert_throws(expectedException, function() { |
| var list = e.classList; |
| var res = list[funcName].apply(list, args); |
| }); |
| } else { |
| var list = e.classList; |
| var res = list[funcName].apply(list, args); |
| } |
| if (obs) { |
| var mutationRecords = obs.takeRecords(); |
| obs.disconnect(); |
| if (shouldThrow) { |
| assert_equals(mutationRecords.length, 0, |
| "There should have been no mutation"); |
| } else if (funcName == "replace") { |
| assert_equals(mutationRecords.length == 1, |
| expectedRes, |
| "Should have a mutation exactly when replace() returns true"); |
| } else { |
| // For other functions, would need to check when exactly |
| // mutations are supposed to happen. |
| } |
| } |
| if (!shouldThrow) { |
| assert_equals(res, expectedRes, "wrong return value"); |
| } |
| |
| var expectedAfter = after; |
| |
| assert_equals(e.getAttribute("class"), expectedAfter, |
| "wrong class after modification"); |
| }, "classList." + funcName + "(" + args.map(format_value).join(", ") + |
| ") with attribute value " + format_value(before) + desc); |
| } |
| |
| function assignToClassListStrict(e) { |
| "use strict"; |
| e.classList = "foo"; |
| e.removeAttribute("class"); |
| } |
| |
| function assignToClassList(e) { |
| var expect = e.classList; |
| e.classList = "foo"; |
| assert_equals(e.classList, expect, |
| "classList should be unchanged after assignment"); |
| e.removeAttribute("class"); |
| } |
| |
| function testClassList(e, desc) { |
| |
| // assignment |
| |
| test(function() { |
| assignToClassListStrict(e); |
| assignToClassList(e); |
| }, "Assigning to classList" + desc); |
| |
| // supports |
| test(function() { |
| assert_throws(TypeError(), function() { |
| e.classList.supports("a"); |
| }) |
| }, ".supports() must throw TypeError" + desc); |
| |
| // length attribute |
| |
| function checkLength(value, length) { |
| test(function() { |
| setClass(e, value); |
| assert_equals(e.classList.length, length); |
| }, "classList.length when " + |
| (value === null ? "removed" : "set to " + format_value(value)) + desc); |
| } |
| |
| checkLength(null, 0); |
| checkLength("", 0); |
| checkLength(" \t \f", 0); |
| checkLength("a", 1); |
| checkLength("a A", 2); |
| checkLength("\r\na\t\f", 1); |
| checkLength("a a", 1); |
| checkLength("a a a a a a", 1); |
| checkLength("a a b b", 2); |
| checkLength("a A B b", 4); |
| checkLength("a b c c b a a b c c", 3); |
| checkLength(" a a b", 2); |
| checkLength("a\tb\nc\fd\re f", 6); |
| |
| // [Stringifies] |
| |
| function checkStringifier(value, expected) { |
| test(function() { |
| setClass(e, value); |
| assert_equals(e.classList.toString(), expected); |
| }, "classList.toString() when " + |
| (value === null ? "removed" : "set to " + format_value(value)) + desc); |
| } |
| |
| checkStringifier(null, ""); |
| checkStringifier("foo", "foo"); |
| checkStringifier(" a a b", " a a b"); |
| |
| // item() method |
| |
| function checkItems(attributeValue, expectedValues) { |
| function checkItemFunction(index, expected) { |
| assert_equals(e.classList.item(index), expected, |
| "classList.item(" + index + ")"); |
| } |
| |
| function checkItemArray(index, expected) { |
| assert_equals(e.classList[index], expected, "classList[" + index + "]"); |
| } |
| |
| test(function() { |
| setClass(e, attributeValue); |
| |
| checkItemFunction(-1, null); |
| checkItemArray(-1, undefined); |
| |
| var i = 0; |
| while (i < expectedValues.length) { |
| checkItemFunction(i, expectedValues[i]); |
| checkItemArray(i, expectedValues[i]); |
| i++; |
| } |
| |
| checkItemFunction(i, null); |
| checkItemArray(i, undefined); |
| |
| checkItemFunction(0xffffffff, null); |
| checkItemArray(0xffffffff, undefined); |
| |
| checkItemFunction(0xfffffffe, null); |
| checkItemArray(0xfffffffe, undefined); |
| }, "classList.item() when set to " + format_value(attributeValue) + desc); |
| } |
| |
| checkItems(null, []); |
| checkItems("a", ["a"]); |
| checkItems("aa AA aa", ["aa", "AA"]); |
| checkItems("a b", ["a", "b"]); |
| checkItems(" a a b", ["a", "b"]); |
| checkItems("\t\n\f\r a\t\n\f\r b\t\n\f\r ", ["a", "b"]); |
| |
| // contains() method |
| |
| function checkContains(attributeValue, args, expectedRes) { |
| if (!Array.isArray(expectedRes)) { |
| expectedRes = Array(args.length).fill(expectedRes); |
| } |
| setClass(e, attributeValue); |
| for (var i = 0; i < args.length; i++) { |
| test(function() { |
| assert_equals(e.classList.contains(args[i]), expectedRes[i], |
| "classList.contains(\"" + args[i] + "\")"); |
| }, "classList.contains(" + format_value(args[i]) + ") when set to " + |
| format_value(attributeValue) + desc); |
| } |
| } |
| |
| checkContains(null, ["a", "", " "], false); |
| checkContains("", ["a"], false); |
| |
| checkContains("a", ["a"], true); |
| checkContains("a", ["aa", "b", "A", "a.", "a)",, "a'", 'a"', "a$", "a~", |
| "a?", "a\\"], false); |
| |
| // All "ASCII whitespace" per spec, before and after |
| checkContains("a", ["a\t", "\ta", "a\n", "\na", "a\f", "\fa", "a\r", "\ra", |
| "a ", " a"], false); |
| |
| checkContains("aa AA", ["aa", "AA", "aA"], [true, true, false]); |
| checkContains("a a a", ["a", "aa", "b"], [true, false, false]); |
| checkContains("a b c", ["a", "b"], true); |
| |
| checkContains("null undefined", [null, undefined], true); |
| checkContains("\t\n\f\r a\t\n\f\r b\t\n\f\r ", ["a", "b"], true); |
| |
| // add() method |
| |
| function checkAdd(before, argument, after, param) { |
| var expectedException = undefined; |
| var noop = false; |
| if (param == "noop") { |
| noop = true; |
| } else { |
| expectedException = param; |
| } |
| checkModification(e, "add", argument, undefined, before, after, |
| expectedException, desc); |
| // Also check force toggle. The only difference is that it doesn't run the |
| // update steps for a no-op. |
| if (!Array.isArray(argument)) { |
| checkModification(e, "toggle", [argument, true], true, before, |
| noop ? before : after, expectedException, desc); |
| } |
| } |
| |
| checkAdd(null, "", null, "SyntaxError"); |
| checkAdd(null, ["a", ""], null, "SyntaxError"); |
| checkAdd(null, " ", null, "InvalidCharacterError"); |
| checkAdd(null, "\ta", null, "InvalidCharacterError"); |
| checkAdd(null, "a\t", null, "InvalidCharacterError"); |
| checkAdd(null, "\na", null, "InvalidCharacterError"); |
| checkAdd(null, "a\n", null, "InvalidCharacterError"); |
| checkAdd(null, "\fa", null, "InvalidCharacterError"); |
| checkAdd(null, "a\f", null, "InvalidCharacterError"); |
| checkAdd(null, "\ra", null, "InvalidCharacterError"); |
| checkAdd(null, "a\r", null, "InvalidCharacterError"); |
| checkAdd(null, " a", null, "InvalidCharacterError"); |
| checkAdd(null, "a ", null, "InvalidCharacterError"); |
| checkAdd(null, ["a", " "], null, "InvalidCharacterError"); |
| checkAdd(null, ["a", "aa "], null, "InvalidCharacterError"); |
| |
| checkAdd("a", "a", "a"); |
| checkAdd("aa", "AA", "aa AA"); |
| checkAdd("a b c", "a", "a b c"); |
| checkAdd("a a a b", "a", "a b", "noop"); |
| checkAdd(null, "a", "a"); |
| checkAdd("", "a", "a"); |
| checkAdd(" ", "a", "a"); |
| checkAdd(" \f", "a", "a"); |
| checkAdd("a", "b", "a b"); |
| checkAdd("a b c", "d", "a b c d"); |
| checkAdd("a b c ", "d", "a b c d"); |
| checkAdd(" a a b", "c", "a b c"); |
| checkAdd(" a a b", "a", "a b", "noop"); |
| checkAdd("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "c", "a b c"); |
| |
| // multiple add |
| checkAdd("a b c ", ["d", "e"], "a b c d e"); |
| checkAdd("a b c ", ["a", "a"], "a b c"); |
| checkAdd("a b c ", ["d", "d"], "a b c d"); |
| checkAdd("a b c a ", [], "a b c"); |
| checkAdd(null, ["a", "b"], "a b"); |
| checkAdd("", ["a", "b"], "a b"); |
| |
| checkAdd(null, null, "null"); |
| checkAdd(null, undefined, "undefined"); |
| |
| // remove() method |
| |
| function checkRemove(before, argument, after, param) { |
| var expectedException = undefined; |
| var noop = false; |
| if (param == "noop") { |
| noop = true; |
| } else { |
| expectedException = param; |
| } |
| checkModification(e, "remove", argument, undefined, before, after, |
| expectedException, desc); |
| // Also check force toggle. The only difference is that it doesn't run the |
| // update steps for a no-op. |
| if (!Array.isArray(argument)) { |
| checkModification(e, "toggle", [argument, false], false, before, |
| noop ? before : after, expectedException, desc); |
| } |
| } |
| |
| checkRemove(null, "", null, "SyntaxError"); |
| checkRemove(null, " ", null, "InvalidCharacterError"); |
| checkRemove("\ta", "\ta", "\ta", "InvalidCharacterError"); |
| checkRemove("a\t", "a\t", "a\t", "InvalidCharacterError"); |
| checkRemove("\na", "\na", "\na", "InvalidCharacterError"); |
| checkRemove("a\n", "a\n", "a\n", "InvalidCharacterError"); |
| checkRemove("\fa", "\fa", "\fa", "InvalidCharacterError"); |
| checkRemove("a\f", "a\f", "a\f", "InvalidCharacterError"); |
| checkRemove("\ra", "\ra", "\ra", "InvalidCharacterError"); |
| checkRemove("a\r", "a\r", "a\r", "InvalidCharacterError"); |
| checkRemove(" a", " a", " a", "InvalidCharacterError"); |
| checkRemove("a ", "a ", "a ", "InvalidCharacterError"); |
| checkRemove("aa ", "aa ", null, "InvalidCharacterError"); |
| |
| checkRemove(null, "a", null); |
| checkRemove("", "a", ""); |
| checkRemove("a b c", "d", "a b c", "noop"); |
| checkRemove("a b c", "A", "a b c", "noop"); |
| checkRemove(" a a a ", "a", ""); |
| checkRemove("a b", "a", "b"); |
| checkRemove("a b ", "a", "b"); |
| checkRemove("a a b", "a", "b"); |
| checkRemove("aa aa bb", "aa", "bb"); |
| checkRemove("a a b a a c a a", "a", "b c"); |
| |
| checkRemove("a b c", "b", "a c"); |
| checkRemove("aaa bbb ccc", "bbb", "aaa ccc"); |
| checkRemove(" a b c ", "b", "a c"); |
| checkRemove("a b b b c", "b", "a c"); |
| |
| checkRemove("a b c", "c", "a b"); |
| checkRemove(" a b c ", "c", "a b"); |
| checkRemove("a b c c c", "c", "a b"); |
| |
| checkRemove("a b a c a d a", "a", "b c d"); |
| checkRemove("AA BB aa CC AA dd aa", "AA", "BB aa CC dd"); |
| |
| checkRemove("\ra\na\ta\f", "a", ""); |
| checkRemove("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "a", "b"); |
| |
| // multiple remove |
| checkRemove("a b c ", ["d", "e"], "a b c"); |
| checkRemove("a b c ", ["a", "b"], "c"); |
| checkRemove("a b c ", ["a", "c"], "b"); |
| checkRemove("a b c ", ["a", "a"], "b c"); |
| checkRemove("a b c ", ["d", "d"], "a b c"); |
| checkRemove("a b c ", [], "a b c"); |
| checkRemove(null, ["a", "b"], null); |
| checkRemove("", ["a", "b"], ""); |
| checkRemove("a a", [], "a"); |
| |
| checkRemove("null", null, ""); |
| checkRemove("undefined", undefined, ""); |
| |
| // toggle() method |
| |
| function checkToggle(before, argument, expectedRes, after, expectedException) { |
| checkModification(e, "toggle", argument, expectedRes, before, after, |
| expectedException, desc); |
| } |
| |
| checkToggle(null, "", null, null, "SyntaxError"); |
| checkToggle(null, "aa ", null, null, "InvalidCharacterError"); |
| |
| checkToggle(null, "a", true, "a"); |
| checkToggle("", "a", true, "a"); |
| checkToggle(" ", "a", true, "a"); |
| checkToggle(" \f", "a", true, "a"); |
| checkToggle("a", "b", true, "a b"); |
| checkToggle("a", "A", true, "a A"); |
| checkToggle("a b c", "d", true, "a b c d"); |
| checkToggle(" a a b", "d", true, "a b d"); |
| |
| checkToggle("a", "a", false, ""); |
| checkToggle(" a a a ", "a", false, ""); |
| checkToggle(" A A A ", "a", true, "A a"); |
| checkToggle(" a b c ", "b", false, "a c"); |
| checkToggle(" a b c b b", "b", false, "a c"); |
| checkToggle(" a b c ", "c", false, "a b"); |
| checkToggle(" a b c ", "a", false, "b c"); |
| checkToggle(" a a b", "b", false, "a"); |
| checkToggle("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "a", false, "b"); |
| checkToggle("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "c", true, "a b c"); |
| |
| checkToggle("null", null, false, ""); |
| checkToggle("", null, true, "null"); |
| checkToggle("undefined", undefined, false, ""); |
| checkToggle("", undefined, true, "undefined"); |
| |
| |
| // replace() method |
| function checkReplace(before, token, newToken, expectedRes, after, expectedException) { |
| checkModification(e, "replace", [token, newToken], expectedRes, before, |
| after, expectedException, desc); |
| } |
| |
| checkReplace(null, "", "a", null, null, "SyntaxError"); |
| checkReplace(null, "", " ", null, null, "SyntaxError"); |
| checkReplace(null, " ", "a", null, null, "InvalidCharacterError"); |
| checkReplace(null, "\ta", "b", null, null, "InvalidCharacterError"); |
| checkReplace(null, "a\t", "b", null, null, "InvalidCharacterError"); |
| checkReplace(null, "\na", "b", null, null, "InvalidCharacterError"); |
| checkReplace(null, "a\n", "b", null, null, "InvalidCharacterError"); |
| checkReplace(null, "\fa", "b", null, null, "InvalidCharacterError"); |
| checkReplace(null, "a\f", "b", null, null, "InvalidCharacterError"); |
| checkReplace(null, "\ra", "b", null, null, "InvalidCharacterError"); |
| checkReplace(null, "a\r", "b", null, null, "InvalidCharacterError"); |
| checkReplace(null, " a", "b", null, null, "InvalidCharacterError"); |
| checkReplace(null, "a ", "b", null, null, "InvalidCharacterError"); |
| |
| checkReplace(null, "a", "", null, null, "SyntaxError"); |
| checkReplace(null, " ", "", null, null, "SyntaxError"); |
| checkReplace(null, "a", " ", null, null, "InvalidCharacterError"); |
| checkReplace(null, "b", "\ta", null, null, "InvalidCharacterError"); |
| checkReplace(null, "b", "a\t", null, null, "InvalidCharacterError"); |
| checkReplace(null, "b", "\na", null, null, "InvalidCharacterError"); |
| checkReplace(null, "b", "a\n", null, null, "InvalidCharacterError"); |
| checkReplace(null, "b", "\fa", null, null, "InvalidCharacterError"); |
| checkReplace(null, "b", "a\f", null, null, "InvalidCharacterError"); |
| checkReplace(null, "b", "\ra", null, null, "InvalidCharacterError"); |
| checkReplace(null, "b", "a\r", null, null, "InvalidCharacterError"); |
| checkReplace(null, "b", " a", null, null, "InvalidCharacterError"); |
| checkReplace(null, "b", "a ", null, null, "InvalidCharacterError"); |
| |
| checkReplace("a", "a", "a", true, "a"); |
| checkReplace("a", "a", "b", true, "b"); |
| checkReplace("a", "A", "b", false, "a"); |
| checkReplace("a b", "b", "A", true, "a A"); |
| checkReplace("a b", "c", "a", false, "a b"); |
| checkReplace("a b c", "d", "e", false, "a b c"); |
| // https://github.com/whatwg/dom/issues/443 |
| checkReplace("a a a b", "a", "a", true, "a b"); |
| checkReplace("a a a b", "c", "d", false, "a a a b"); |
| checkReplace(null, "a", "b", false, null); |
| checkReplace("", "a", "b", false, ""); |
| checkReplace(" ", "a", "b", false, " "); |
| checkReplace(" a \f", "a", "b", true, "b"); |
| checkReplace("a b c", "b", "d", true, "a d c"); |
| checkReplace("a b c", "c", "a", true, "a b"); |
| checkReplace("c b a", "c", "a", true, "a b"); |
| checkReplace("a b a", "a", "c", true, "c b"); |
| checkReplace("a b a", "b", "c", true, "a c"); |
| checkReplace(" a a b", "a", "c", true, "c b"); |
| checkReplace(" a a b", "b", "c", true, "a c"); |
| checkReplace("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "a", "c", true, "c b"); |
| checkReplace("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "b", "c", true, "a c"); |
| |
| checkReplace("a null", null, "b", true, "a b"); |
| checkReplace("a b", "a", null, true, "null b"); |
| checkReplace("a undefined", undefined, "b", true, "a b"); |
| checkReplace("a b", "a", undefined, true, "undefined b"); |
| } |
| |
| var content = document.getElementById("content"); |
| |
| var htmlNode = document.createElement("div"); |
| content.appendChild(htmlNode); |
| testClassList(htmlNode, " (HTML node)"); |
| |
| var xhtmlNode = document.createElementNS(XHTML_NS, "div"); |
| content.appendChild(xhtmlNode); |
| testClassList(xhtmlNode, " (XHTML node)"); |
| |
| var mathMLNode = document.createElementNS(MATHML_NS, "math"); |
| content.appendChild(mathMLNode); |
| testClassList(mathMLNode, " (MathML node)"); |
| |
| var xmlNode = document.createElementNS(null, "foo"); |
| content.appendChild(xmlNode); |
| testClassList(xmlNode, " (XML node with null namespace)"); |
| |
| var fooNode = document.createElementNS("http://example.org/foo", "foo"); |
| content.appendChild(fooNode); |
| testClassList(fooNode, " (foo node)"); |
| </script> |