|
1 <!DOCTYPE HTML> |
|
2 <html> |
|
3 <!-- |
|
4 https://bugzilla.mozilla.org/show_bug.cgi?id=501257 |
|
5 --> |
|
6 <head> |
|
7 <title>Test for the classList element attribute</title> |
|
8 <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> |
|
9 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> |
|
10 </head> |
|
11 <body> |
|
12 <a target="_blank" href="http://www.whatwg.org/specs/web-apps/current-work/#dom-classlist">classList DOM attribute</a> |
|
13 <p id="display"></p> |
|
14 <div id="content" style="display: none"> |
|
15 </div> |
|
16 <pre id="test"> |
|
17 <script type="application/javascript"> |
|
18 |
|
19 /** Test for Bug 501257 **/ |
|
20 |
|
21 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; |
|
22 const SVG_NS = "http://www.w3.org/2000/svg"; |
|
23 const XHTML_NS = "http://www.w3.org/1999/xhtml" |
|
24 const MATHML_NS = "http://www.w3.org/1998/Math/MathML"; |
|
25 |
|
26 var gMutationEvents = []; |
|
27 |
|
28 function onAttrModified(event) { |
|
29 is(event.attrName, "class", "mutation on unexpected attribute"); |
|
30 |
|
31 gMutationEvents.push({ |
|
32 attrChange: event.attrChange, |
|
33 prevValue: event.prevValue, |
|
34 newValue: event.newValue, |
|
35 }); |
|
36 } |
|
37 |
|
38 function checkModification(e, funcName, args, expectedRes, before, after, expectedException) { |
|
39 if (!Array.isArray(args)) { |
|
40 args = [args]; |
|
41 } |
|
42 |
|
43 var shouldThrow = typeof(expectedException) === "string"; |
|
44 if (shouldThrow) { |
|
45 // If an exception is thrown, the class attribute shouldn't change. |
|
46 after = before; |
|
47 } |
|
48 if (before === null) |
|
49 e.removeAttribute("class"); |
|
50 else |
|
51 e.setAttribute("class", before); |
|
52 |
|
53 var contextMsg = "(checkModification: funcName=" + funcName + ",args=" + |
|
54 JSON.stringify(args) + ",expectedRes=" + expectedRes + |
|
55 ",before=" + before + ",after=" + after + ")"; |
|
56 |
|
57 gMutationEvents = []; |
|
58 e.addEventListener("DOMAttrModified", onAttrModified, false); |
|
59 try { |
|
60 var list = e.classList; |
|
61 var res = list[funcName].apply(list, args); |
|
62 if (shouldThrow) |
|
63 ok(false, "classList modification didn't throw " + contextMsg); |
|
64 } catch (e) { |
|
65 if (!shouldThrow) |
|
66 ok(false, "classList modification threw an exception " + contextMsg); |
|
67 is(e.name, expectedException, "wrong exception thrown " + contextMsg); |
|
68 } |
|
69 e.removeEventListener("DOMAttrModified", onAttrModified, false); |
|
70 if (expectedRes !== null) |
|
71 is(res, expectedRes, "wrong return value from " + funcName + |
|
72 " " + contextMsg); |
|
73 |
|
74 var expectedAfter = after; |
|
75 // XUL returns an empty string when getting a nonexistent class attribute. |
|
76 if (e.namespaceURI == XUL_NS && expectedAfter === null) |
|
77 expectedAfter = ""; |
|
78 |
|
79 is(e.getAttribute("class"), expectedAfter, "wrong class after modification " + |
|
80 contextMsg); |
|
81 var expectedMutation = before != after; |
|
82 is(gMutationEvents.length, expectedMutation ? 1 : 0, |
|
83 "unexpected mutation event count " + contextMsg); |
|
84 if (expectedMutation && gMutationEvents.length) { |
|
85 is(gMutationEvents[0].attrChange, |
|
86 before == null ? MutationEvent.ADDITION : MutationEvent.MODIFICATION, |
|
87 "wrong type of attribute change " + contextMsg); |
|
88 // If there wasn't any previous attribute, prevValue will return an empty |
|
89 // string. |
|
90 var expectedPrevValue = before === null ? "" : before; |
|
91 is(gMutationEvents[0].prevValue, expectedPrevValue, |
|
92 "wrong previous value " + contextMsg); |
|
93 is(gMutationEvents[0].newValue, after, "wrong new value " + contextMsg); |
|
94 } |
|
95 } |
|
96 |
|
97 function assignToClassListStrict(e) { |
|
98 "use strict"; |
|
99 try { |
|
100 e.classList = "foo"; |
|
101 ok(false, "assigning to classList didn't throw"); |
|
102 } catch (e) { } |
|
103 } |
|
104 |
|
105 function assignToClassList(e) { |
|
106 try { |
|
107 var expect = e.classList; |
|
108 e.classList = "foo"; |
|
109 is(e.classList, expect, "classList should be unchanged after assignment"); |
|
110 } catch (e) { |
|
111 ok(false, "assigning to classList threw"); |
|
112 } |
|
113 } |
|
114 |
|
115 function testClassList(e) { |
|
116 |
|
117 // basic tests |
|
118 |
|
119 isnot(e.classList, undefined, "no classList attribute"); |
|
120 is(typeof(e.classList.contains), "function", |
|
121 "no classList.contains function"); |
|
122 is(typeof(e.classList.add), "function", "no classList.add function"); |
|
123 is(typeof(e.classList.remove), "function", "no classList.remove function"); |
|
124 is(typeof(e.classList.toggle), "function", "no classList.toggle function"); |
|
125 |
|
126 assignToClassListStrict(e); |
|
127 assignToClassList(e); |
|
128 |
|
129 // length attribute |
|
130 |
|
131 is(e.classList.length, 0, "wrong classList.length value"); |
|
132 e.setAttribute("class", ""); |
|
133 is(e.classList.length, 0, "wrong classList.length value"); |
|
134 e.setAttribute("class", " \t \f"); |
|
135 is(e.classList.length, 0, "wrong classList.length value"); |
|
136 |
|
137 e.setAttribute("class", "a"); |
|
138 is(e.classList.length, 1, "wrong classList.length value"); |
|
139 e.setAttribute("class", "a A"); |
|
140 is(e.classList.length, 2, "wrong classList.length value"); |
|
141 e.setAttribute("class", "\r\na\t\f"); |
|
142 is(e.classList.length, 1, "wrong classList.length value"); |
|
143 |
|
144 e.setAttribute("class", "a a"); |
|
145 is(e.classList.length, 2, "wrong classList.length value"); |
|
146 |
|
147 e.setAttribute("class", "a a a a a a"); |
|
148 is(e.classList.length, 6, "wrong classList.length value"); |
|
149 |
|
150 e.setAttribute("class", "a a b b"); |
|
151 is(e.classList.length, 4, "wrong classList.length value"); |
|
152 |
|
153 e.setAttribute("class", "a A B b"); |
|
154 is(e.classList.length, 4, "wrong classList.length value"); |
|
155 |
|
156 e.setAttribute("class", "a b c c b a a b c c"); |
|
157 is(e.classList.length, 10, "wrong classList.length value"); |
|
158 |
|
159 // [Stringifies] |
|
160 |
|
161 ok(DOMTokenList.prototype.hasOwnProperty("toString"), |
|
162 "Should have own toString on DOMTokenList") |
|
163 ok(!DOMSettableTokenList.prototype.hasOwnProperty("toString"), |
|
164 "Should not have own toString on DOMSettableTokenList") |
|
165 |
|
166 e.removeAttribute("class"); |
|
167 is(e.classList.toString(), "", "wrong classList.toString() value"); |
|
168 is(e.classList + "", "", "wrong classList string conversion value"); |
|
169 |
|
170 e.setAttribute("class", "foo"); |
|
171 is(e.classList.toString(), "foo", "wrong classList.toString() value"); |
|
172 is(e.classList + "", "foo", "wrong classList string conversion value"); |
|
173 |
|
174 // item() method |
|
175 |
|
176 e.setAttribute("class", "a"); |
|
177 is(e.classList.item(-1), null, "wrong classList.item() result"); |
|
178 is(e.classList[-1], undefined, "wrong classList[] result"); |
|
179 is(e.classList.item(0), "a", "wrong classList.item() result"); |
|
180 is(e.classList[0], "a", "wrong classList[] result"); |
|
181 is(e.classList.item(1), null, "wrong classList.item() result"); |
|
182 is(e.classList[1], undefined, "wrong classList[] result"); |
|
183 |
|
184 e.setAttribute("class", "aa AA aa"); |
|
185 is(e.classList.item(-1), null, "wrong classList.item() result"); |
|
186 is(e.classList[-1], undefined, "wrong classList[] result"); |
|
187 is(e.classList.item(0), "aa", "wrong classList.item() result"); |
|
188 is(e.classList[0], "aa", "wrong classList[] result"); |
|
189 is(e.classList.item(1), "AA", "wrong classList.item() result"); |
|
190 is(e.classList[1], "AA", "wrong classList[] result"); |
|
191 is(e.classList.item(2), "aa", "wrong classList.item() result"); |
|
192 is(e.classList[2], "aa", "wrong classList[] result"); |
|
193 is(e.classList.item(3), null, "wrong classList.item() result"); |
|
194 is(e.classList[3], undefined, "wrong classList[] result"); |
|
195 is(e.classList.item(0xffffffff), null, "wrong classList.item() result"); |
|
196 is(e.classList[0xffffffff], undefined, "wrong classList[] result"); |
|
197 is(e.classList.item(0xfffffffe), null, "wrong classList.item() result"); |
|
198 is(e.classList[0xffffffe], undefined, "wrong classList[] result"); |
|
199 |
|
200 e.setAttribute("class", "a b"); |
|
201 is(e.classList.item(-1), null, "wrong classList.item() result"); |
|
202 is(e.classList[-1], undefined, "wrong classList[] result"); |
|
203 is(e.classList.item(0), "a", "wrong classList.item() result"); |
|
204 is(e.classList[0], "a", "wrong classList[] result"); |
|
205 is(e.classList.item(1), "b", "wrong classList.item() result"); |
|
206 is(e.classList[1], "b", "wrong classList[] result"); |
|
207 is(e.classList.item(2), null, "wrong classList.item() result"); |
|
208 is(e.classList[2], undefined, "wrong classList[] result"); |
|
209 |
|
210 // contains() method |
|
211 |
|
212 e.removeAttribute("class"); |
|
213 is(e.classList.contains("a"), false, "wrong classList.contains() result"); |
|
214 try { |
|
215 e.classList.contains(""); |
|
216 ok(false, "classList.contains() didn't throw"); |
|
217 } catch (e) { |
|
218 is(e.name, "SyntaxError", "wrong exception thrown"); |
|
219 is(e.code, DOMException.SYNTAX_ERR, "wrong exception thrown"); |
|
220 } |
|
221 try { |
|
222 e.classList.contains(" "); |
|
223 ok(false, "classList.contains() didn't throw"); |
|
224 } catch (e) { |
|
225 is(e.name, "InvalidCharacterError", "wrong exception thrown"); |
|
226 is(e.code, DOMException.INVALID_CHARACTER_ERR, "wrong exception thrown"); |
|
227 } |
|
228 try { |
|
229 e.classList.contains("aa "); |
|
230 ok(false, "classList.contains() didn't throw"); |
|
231 } catch (e) { |
|
232 is(e.name, "InvalidCharacterError", "wrong exception thrown"); |
|
233 is(e.code, DOMException.INVALID_CHARACTER_ERR, "wrong exception thrown"); |
|
234 } |
|
235 |
|
236 e.setAttribute("class", ""); |
|
237 is(e.classList.contains("a"), false, "wrong classList.contains() result"); |
|
238 |
|
239 e.setAttribute("class", "a"); |
|
240 is(e.classList.contains("a"), true, "wrong classList.contains() result"); |
|
241 is(e.classList.contains("aa"), false, "wrong classList.contains() result"); |
|
242 is(e.classList.contains("b"), false, "wrong classList.contains() result"); |
|
243 |
|
244 e.setAttribute("class", "aa AA"); |
|
245 is(e.classList.contains("aa"), true, "wrong classList.contains() result"); |
|
246 is(e.classList.contains("AA"), true, "wrong classList.contains() result"); |
|
247 is(e.classList.contains("aA"), false, "wrong classList.contains() result"); |
|
248 |
|
249 e.setAttribute("class", "a a a"); |
|
250 is(e.classList.contains("a"), true, "wrong classList.contains() result"); |
|
251 is(e.classList.contains("aa"), false, "wrong classList.contains() result"); |
|
252 is(e.classList.contains("b"), false, "wrong classList.contains() result"); |
|
253 |
|
254 e.setAttribute("class", "a b c"); |
|
255 is(e.classList.contains("a"), true, "wrong classList.contains() result"); |
|
256 is(e.classList.contains("b"), true, "wrong classList.contains() result"); |
|
257 |
|
258 // Test for bug 530171 |
|
259 e.setAttribute("class", "null undefined"); |
|
260 is(e.classList.contains(null), true, "wrong classList.contains() result"); |
|
261 is(e.classList.contains(undefined), true, "wrong classList.contains() result"); |
|
262 |
|
263 // add() method |
|
264 |
|
265 function checkAdd(before, argument, after, expectedException) { |
|
266 checkModification(e, "add", argument, null, before, after, expectedException); |
|
267 } |
|
268 |
|
269 checkAdd(null, "", null, "SyntaxError"); |
|
270 checkAdd(null, ["a", ""], null, "SyntaxError"); |
|
271 checkAdd(null, " ", null, "InvalidCharacterError"); |
|
272 checkAdd(null, ["a", " "], null, "InvalidCharacterError"); |
|
273 checkAdd(null, ["a", "aa "], null, "InvalidCharacterError"); |
|
274 |
|
275 checkAdd("a", "a", "a"); |
|
276 checkAdd("aa", "AA", "aa AA"); |
|
277 checkAdd("a b c", "a", "a b c"); |
|
278 checkAdd("a a a b", "a", "a a a b"); |
|
279 checkAdd(null, "a", "a"); |
|
280 checkAdd("", "a", "a"); |
|
281 checkAdd(" ", "a", " a"); |
|
282 checkAdd(" \f", "a", " \fa"); |
|
283 checkAdd("a", "b", "a b"); |
|
284 checkAdd("a b c", "d", "a b c d"); |
|
285 checkAdd("a b c ", "d", "a b c d"); |
|
286 |
|
287 // multiple add |
|
288 checkAdd("a b c ", ["d", "e"], "a b c d e"); |
|
289 checkAdd("a b c ", ["a", "a"], "a b c "); |
|
290 checkAdd("a b c ", ["d", "d"], "a b c d"); |
|
291 checkAdd("a b c ", [], "a b c "); |
|
292 checkAdd(null, ["a", "b"], "a b"); |
|
293 checkAdd("", ["a", "b"], "a b"); |
|
294 |
|
295 // Test for bug 530171 |
|
296 checkAdd(null, null, "null"); |
|
297 checkAdd(null, undefined, "undefined"); |
|
298 |
|
299 // remove() method |
|
300 |
|
301 function checkRemove(before, argument, after, expectedException) { |
|
302 checkModification(e, "remove", argument, null, before, after, expectedException); |
|
303 } |
|
304 |
|
305 checkRemove(null, "", null, "SyntaxError"); |
|
306 checkRemove(null, " ", null, "InvalidCharacterError"); |
|
307 checkRemove(null, "aa ", null, "InvalidCharacterError"); |
|
308 |
|
309 checkRemove(null, "a", null); |
|
310 checkRemove("", "a", ""); |
|
311 checkRemove("a b c", "d", "a b c"); |
|
312 checkRemove("a b c", "A", "a b c"); |
|
313 checkRemove(" a a a ", "a", ""); |
|
314 checkRemove("a b", "a", "b"); |
|
315 checkRemove("a b ", "a", "b "); |
|
316 checkRemove("a a b", "a", "b"); |
|
317 checkRemove("aa aa bb", "aa", "bb"); |
|
318 checkRemove("a a b a a c a a", "a", "b c"); |
|
319 |
|
320 checkRemove("a b c", "b", "a c"); |
|
321 checkRemove("aaa bbb ccc", "bbb", "aaa ccc"); |
|
322 checkRemove(" a b c ", "b", " a c "); |
|
323 checkRemove("a b b b c", "b", "a c"); |
|
324 |
|
325 checkRemove("a b c", "c", "a b"); |
|
326 checkRemove(" a b c ", "c", " a b"); |
|
327 checkRemove("a b c c c", "c", "a b"); |
|
328 |
|
329 checkRemove("a b a c a d a", "a", "b c d"); |
|
330 checkRemove("AA BB aa CC AA dd aa", "AA", "BB aa CC dd aa"); |
|
331 |
|
332 checkRemove("\ra\na\ta\f", "a", ""); |
|
333 |
|
334 // multiple remove |
|
335 checkRemove("a b c ", ["d", "e"], "a b c "); |
|
336 checkRemove("a b c ", ["a", "b"], "c "); |
|
337 checkRemove("a b c ", ["a", "c"], "b"); |
|
338 checkRemove("a b c ", ["a", "a"], "b c "); |
|
339 checkRemove("a b c ", ["d", "d"], "a b c "); |
|
340 checkRemove("a b c ", [], "a b c "); |
|
341 checkRemove(null, ["a", "b"], null); |
|
342 checkRemove("", ["a", "b"], ""); |
|
343 |
|
344 // Test for bug 530171 |
|
345 checkRemove("null", null, ""); |
|
346 checkRemove("undefined", undefined, ""); |
|
347 |
|
348 // toggle() method |
|
349 |
|
350 function checkToggle(before, argument, expectedRes, after, expectedException) { |
|
351 checkModification(e, "toggle", argument, expectedRes, before, after, expectedException); |
|
352 } |
|
353 |
|
354 checkToggle(null, "", null, null, "SyntaxError"); |
|
355 checkToggle(null, "aa ", null, null, "InvalidCharacterError"); |
|
356 |
|
357 checkToggle(null, "a", true, "a"); |
|
358 checkToggle("", "a", true, "a"); |
|
359 checkToggle(" ", "a", true, " a"); |
|
360 checkToggle(" \f", "a", true, " \fa"); |
|
361 checkToggle("a", "b", true, "a b"); |
|
362 checkToggle("a", "A", true, "a A"); |
|
363 checkToggle("a b c", "d", true, "a b c d"); |
|
364 checkToggle("a b c", "d", true, "a b c d"); |
|
365 |
|
366 checkToggle("a", "a", false, ""); |
|
367 checkToggle(" a a a ", "a", false, ""); |
|
368 checkToggle(" A A A ", "a", true, " A A A a"); |
|
369 checkToggle(" a b c ", "b", false, " a c "); |
|
370 checkToggle(" a b c b b", "b", false, " a c"); |
|
371 checkToggle(" a b c ", "c", false, " a b"); |
|
372 checkToggle(" a b c ", "a", false, "b c "); |
|
373 |
|
374 // Test for bug 530171 |
|
375 checkToggle("null", null, false, ""); |
|
376 checkToggle("", null, true, "null"); |
|
377 checkToggle("undefined", undefined, false, ""); |
|
378 checkToggle("", undefined, true, "undefined"); |
|
379 |
|
380 |
|
381 // tests for the force argument handling |
|
382 |
|
383 function checkForceToggle(before, argument, force, expectedRes, after, expectedException) { |
|
384 checkModification(e, "toggle", [argument, force], expectedRes, before, after, expectedException); |
|
385 } |
|
386 |
|
387 checkForceToggle("", "a", true, true, "a"); |
|
388 checkForceToggle("a", "a", true, true, "a"); |
|
389 checkForceToggle("a", "b", true, true, "a b"); |
|
390 checkForceToggle("a b", "b", true, true, "a b"); |
|
391 checkForceToggle("", "a", false, false, ""); |
|
392 checkForceToggle("a", "a", false, false, ""); |
|
393 checkForceToggle("a", "b", false, false, "a"); |
|
394 checkForceToggle("a b", "b", false, false, "a"); |
|
395 } |
|
396 |
|
397 var content = document.getElementById("content"); |
|
398 |
|
399 var htmlNode = document.createElement("div"); |
|
400 content.appendChild(htmlNode); |
|
401 testClassList(htmlNode); |
|
402 |
|
403 var xhtmlNode = document.createElementNS(XHTML_NS, "div"); |
|
404 content.appendChild(xhtmlNode); |
|
405 testClassList(xhtmlNode); |
|
406 |
|
407 var xulNode = document.createElementNS(XUL_NS, "box"); |
|
408 content.appendChild(xulNode); |
|
409 testClassList(xulNode); |
|
410 |
|
411 var mathMLNode = document.createElementNS(MATHML_NS, "math"); |
|
412 content.appendChild(mathMLNode); |
|
413 testClassList(mathMLNode); |
|
414 |
|
415 // Nodes not meant to be styled have a null classList property. |
|
416 |
|
417 var xmlNode = document.createElementNS(null, "foo"); |
|
418 content.appendChild(xmlNode); |
|
419 is(xmlNode.classList, null, "classList is not null for plain XML nodes"); |
|
420 |
|
421 var fooNode = document.createElementNS("http://example.org/foo", "foo"); |
|
422 content.appendChild(fooNode); |
|
423 is(fooNode.classList, null, "classList is not null for nodes in " + |
|
424 " http://example.org/foo namespace"); |
|
425 |
|
426 </script> |
|
427 </pre> |
|
428 </body> |
|
429 </html> |