4 <title>Test for CSS Selectors
</title>
5 <script type=
"text/javascript" src=
"/MochiKit/MochiKit.js"></script>
6 <script type=
"text/javascript" src=
"/tests/SimpleTest/SimpleTest.js"></script>
7 <link rel=
"stylesheet" type=
"text/css" href=
"/tests/SimpleTest/test.css" />
10 <p id=
"display"><iframe id=
"iframe" src=
"about:blank"></iframe></p>
12 <script class=
"testbody" type=
"text/javascript">
14 SimpleTest.waitForExplicitFinish();
18 var iframe = document.getElementById(
"iframe");
19 var ifwin = iframe.contentWindow;
20 var ifdoc = iframe.contentDocument;
22 function setup_style() {
23 var style_elem = ifdoc.createElement(
"style");
24 style_elem.setAttribute(
"type",
"text/css");
25 ifdoc.getElementsByTagName(
"head")[
0].appendChild(style_elem);
26 var style_text = ifdoc.createTextNode(
"");
27 style_elem.appendChild(style_text);
31 var style_text = setup_style();
36 * selector: the selector to test
37 * body_contents: what to set the body's innerHTML to
38 * match_fn: a function that, given the document object into which
39 * body_contents has been inserted, produces an array of nodes that
40 * should match selector
41 * notmatch_fn: likewise, but for nodes that should not match
43 function test_selector_in_html(selector, body_contents, match_fn, notmatch_fn)
46 if (typeof(body_contents) ==
"string") {
47 ifdoc.body.innerHTML = body_contents;
50 ifdoc.body.innerHTML =
"";
51 body_contents(ifdoc.body);
53 style_text.data = selector +
"{ z-index: " + zi +
" }";
54 var should_match = match_fn(ifdoc);
55 var should_not_match = notmatch_fn(ifdoc);
56 if (should_match.length + should_not_match.length ==
0) {
57 ok(false,
"nothing to check");
60 for (var i =
0; i < should_match.length; ++i) {
61 var e = should_match[i];
62 is(ifwin.getComputedStyle(e,
"").zIndex, zi,
63 "element in " + body_contents +
" matched " + selector);
65 for (var i =
0; i < should_not_match.length; ++i) {
66 var e = should_not_match[i];
67 is(ifwin.getComputedStyle(e,
"").zIndex,
"auto",
68 "element in " + body_contents +
" did not match " + selector);
71 // Now, since we're here, may as well make sure serialization
72 // works correctly. It need not produce the exact same text,
73 // but it should produce a selector that matches the same
76 var ser1 = style_text.parentNode.sheet.cssRules[
0].selectorText;
77 style_text.data = ser1 +
"{ z-index: " + zi +
" }";
78 for (var i =
0; i < should_match.length; ++i) {
79 var e = should_match[i];
80 is(ifwin.getComputedStyle(e,
"").zIndex, zi,
81 "element in " + body_contents +
" matched " + ser1 +
82 " which is the reserialization of " + selector);
84 for (var i =
0; i < should_not_match.length; ++i) {
85 var e = should_not_match[i];
86 is(ifwin.getComputedStyle(e,
"").zIndex,
"auto",
87 "element in " + body_contents +
" did not match " + ser1 +
88 " which is the reserialization of " + selector);
91 // But when we serialize the serialized result, we should get
93 var ser2 = style_text.parentNode.sheet.cssRules[
0].selectorText;
94 is(ser2, ser1,
"parse+serialize of selector \"" + selector +
97 ifdoc.body.innerHTML =
"";
101 function test_parseable(selector)
104 ifdoc.body.innerHTML =
"<p></p>";
105 style_text.data =
"p, " + selector +
"{ z-index: " + zi +
" }";
106 var should_match = ifdoc.getElementsByTagName(
"p")[
0];
107 is(ifwin.getComputedStyle(should_match,
"").zIndex, zi,
108 "selector " + selector +
" was parsed");
109 ifdoc.body.innerHTML =
"";
110 style_text.data =
"";
113 function test_balanced_unparseable(selector)
115 var zi1 = ++gCounter;
116 var zi2 = ++gCounter;
117 ifdoc.body.innerHTML =
"<p></p><div></div>";
118 style_text.data =
"p, " + selector +
"{ z-index: " + zi1 +
" }" +
119 "div { z-index: " + zi2 +
" }";
120 var should_not_match = ifdoc.getElementsByTagName(
"p")[
0];
121 var should_match = ifdoc.getElementsByTagName(
"div")[
0];
122 is(ifwin.getComputedStyle(should_not_match,
"").zIndex,
"auto",
123 "selector " + selector +
" was a parser error");
124 is(ifwin.getComputedStyle(should_match,
"").zIndex, zi2,
125 "selector " + selector +
" error was recovered from");
126 ifdoc.body.innerHTML =
"";
127 style_text.data =
"";
131 test_parseable(
"[attr=\"x\
"]");
132 test_parseable(
"[attr='x']");
133 test_parseable(
"[attr=x]");
134 test_parseable(
"[attr=\"\
"]");
135 test_parseable(
"[attr='']");
136 test_parseable(
"[attr=\"foo bar\
"]");
138 test_balanced_unparseable(
"[attr=]");
139 test_balanced_unparseable(
"[attr=foo bar]");
141 test_selector_in_html(
144 + '
<div lang=
" "></div><div lang=
"\t"></div><div lang=
"\n"></div>',
145 function(doc) { return doc.getElementsByTagName(
"p"); },
146 function(doc) { return doc.getElementsByTagName(
"div"); }
149 // [attr~= ] selector
150 test_parseable(
"[attr~=\"x\
"]");
151 test_parseable(
"[attr~='x']");
152 test_parseable(
"[attr~=x]");
153 test_parseable(
"[attr~=\"\
"]");
154 test_parseable(
"[attr~='']");
155 test_parseable(
"[attr~=\"foo bar\
"]");
157 test_balanced_unparseable(
"[attr~=]");
158 test_balanced_unparseable(
"[attr~=foo bar]");
160 test_selector_in_html(
162 '
<div class=
"x x"></div><div class=
"x"></div><div class=
"x\tx"></div>div
class=
"x\nx"></div>',
163 function(doc) { return []; },
164 function(doc) { return doc.getElementsByTagName(
"div"); }
168 test_parseable('[attr|=
"x"]');
169 test_parseable(
"[attr|='x']");
170 test_parseable('[attr|=x]');
172 test_parseable('[attr|=
""]');
173 test_parseable(
"[attr|='']");
174 test_balanced_unparseable('[attr|=]');
176 test_selector_in_html(
178 '
<p lang=
""></p><p lang=
"-"></p><p lang=
"-GB"></p>'
179 + '
<div lang=
"en-GB"></div><div lang=
"en-"></div>',
180 function(doc) { return doc.getElementsByTagName(
"p"); },
181 function(doc) { return doc.getElementsByTagName(
"div"); }
184 // [attr$= ] selector
185 test_parseable(
"[attr$=\"x\
"]");
186 test_parseable(
"[attr$='x']");
187 test_parseable(
"[attr$=x]");
188 test_parseable(
"[attr$=\"\
"]");
189 test_parseable(
"[attr$='']");
190 test_parseable(
"[attr$=\"foo bar\
"]");
192 test_balanced_unparseable(
"[attr$=]");
193 test_balanced_unparseable(
"[attr$=foo bar]");
195 // [attr^= ] selector
196 test_parseable(
"[attr^=\"x\
"]");
197 test_parseable(
"[attr^='x']");
198 test_parseable(
"[attr^=x]");
199 test_parseable(
"[attr^=\"\
"]");
200 test_parseable(
"[attr^='']");
201 test_parseable(
"[attr^=\"foo bar\
"]");
203 test_balanced_unparseable(
"[attr^=]");
204 test_balanced_unparseable(
"[attr^=foo bar]");
206 // attr[*= ] selector
207 test_parseable(
"[attr*=\"x\
"]");
208 test_parseable(
"[attr*='x']");
209 test_parseable(
"[attr*=x]");
210 test_parseable(
"[attr*=\"\
"]");
211 test_parseable(
"[attr*='']");
212 test_parseable(
"[attr*=\"foo bar\
"]");
214 test_balanced_unparseable(
"[attr*=]");
215 test_balanced_unparseable(
"[attr*=foo bar]");
219 test_selector_in_html(
221 "<div></div><div><div><p>match</p></div></div>",
222 function(doc) { return doc.getElementsByTagName(
"p"); },
223 function(doc) { return []; }
227 test_selector_in_html(
229 "<p attr=\"foo\
">This should not match</p>",
230 function(doc) { return []; },
231 function(doc) { return doc.getElementsByTagName(
"p"); }
233 test_selector_in_html(
234 "div + p[attr~=\"\
"]",
235 "<div>Dummy</div><p attr=\"foo\
">This should not match</p>",
236 function(doc) { return []; },
237 function(doc) { return doc.getElementsByTagName(
"p"); }
239 test_selector_in_html(
241 "<div attr=\"dummy1\
">Dummy</div><div attr=\"dummy2\
">Dummy</div>",
242 function(doc) { return []; },
243 function(doc) { return doc.getElementsByTagName(
"div"); }
245 test_selector_in_html(
247 "<div attr=\"dummy1\
">Dummy</div><div attr=\"dummy2\
">Dummy</div>",
248 function(doc) { return []; },
249 function(doc) { return doc.getElementsByTagName(
"div"); }
252 // :nth-child(), etc.
253 // Follow the whitespace rules as proposed in
254 // http://lists.w3.org/Archives/Public/www-style/
2008Mar/
0121.html
255 test_balanced_unparseable(
":nth-child()");
256 test_balanced_unparseable(
":nth-of-type( )");
257 test_parseable(
":nth-last-child( odd)");
258 test_parseable(
":nth-last-of-type(even )");
259 test_parseable(
":nth-child(n )");
260 test_parseable(
":nth-of-type( 2n)");
261 test_parseable(
":nth-last-child( -n)");
262 test_parseable(
":nth-last-of-type(-2n )");
263 test_balanced_unparseable(
":nth-child(- n)");
264 test_balanced_unparseable(
":nth-of-type(-2 n)");
265 test_balanced_unparseable(
":nth-last-of-type(2n1)");
266 test_balanced_unparseable(
":nth-child(2n++1)");
267 test_balanced_unparseable(
":nth-of-type(2n-+1)");
268 test_balanced_unparseable(
":nth-last-child(2n+-1)");
269 test_balanced_unparseable(
":nth-last-of-type(2n--1)");
270 test_parseable(
":nth-child( 3n + 1 )");
271 test_parseable(
":nth-child( +3n - 2 )");
272 test_parseable(
":nth-child( -n+ 6)");
273 test_parseable(
":nth-child( +6 )");
274 test_balanced_unparseable(
":nth-child(3 n)");
275 test_balanced_unparseable(
":nth-child(+ 2n)");
276 test_balanced_unparseable(
":nth-child(+ 2)");
277 test_parseable(
":nth-child(3)");
278 test_parseable(
":nth-of-type(-3)");
279 test_parseable(
":nth-last-child(+3)");
280 test_parseable(
":nth-last-of-type(0)");
281 test_parseable(
":nth-child(-0)");
282 test_parseable(
":nth-of-type(3n)");
283 test_parseable(
":nth-last-child(-3n)");
284 test_parseable(
":nth-last-of-type(+3n)");
285 test_parseable(
":nth-last-of-type(0n)");
286 test_parseable(
":nth-child(-0n)");
287 test_parseable(
":nth-of-type(n)");
288 test_parseable(
":nth-last-child(-n)");
289 test_parseable(
":nth-last-of-type(2n+1)");
290 test_parseable(
":nth-child(2n-1)");
291 test_parseable(
":nth-of-type(2n+0)");
292 test_parseable(
":nth-last-child(2n-0)");
293 test_parseable(
":nth-child(-0n+0)");
294 test_parseable(
":nth-of-type(n+1)");
295 test_parseable(
":nth-last-child(n-1)");
296 test_parseable(
":nth-last-of-type(-n+1)");
297 test_parseable(
":nth-child(-n-1)");
298 test_balanced_unparseable(
":nth-child(2-n)");
299 test_balanced_unparseable(
":nth-child(2-n-1)");
300 test_balanced_unparseable(
":nth-child(n-2-1)");
302 // exercise the an+b matching logic particularly hard for
303 // :nth-child() (since we know we use the same code for all
4)
304 var seven_ps =
"<p></p><p></p><p></p><p></p><p></p><p></p><p></p>";
305 function pset(indices) { // takes an array of
1-based indices
306 return function pset_filter(doc) {
307 var a = doc.getElementsByTagName(
"p");
309 for (var i in indices)
310 result.push(a[indices[i] -
1]);
314 test_selector_in_html(
":nth-child(0)", seven_ps,
315 pset([]), pset([
1,
2,
3,
4,
5,
6,
7]));
316 test_selector_in_html(
":nth-child(-3)", seven_ps,
317 pset([]), pset([
1,
2,
3,
4,
5,
6,
7]));
318 test_selector_in_html(
":nth-child(3)", seven_ps,
319 pset([
3]), pset([
1,
2,
4,
5,
6,
7]));
320 test_selector_in_html(
":nth-child(0n+3)", seven_ps,
321 pset([
3]), pset([
1,
2,
4,
5,
6,
7]));
322 test_selector_in_html(
":nth-child(-0n+3)", seven_ps,
323 pset([
3]), pset([
1,
2,
4,
5,
6,
7]));
324 test_selector_in_html(
":nth-child(8)", seven_ps,
325 pset([]), pset([
1,
2,
3,
4,
5,
6,
7]));
326 test_selector_in_html(
":nth-child(odd)", seven_ps,
327 pset([
1,
3,
5,
7]), pset([
2,
4,
6]));
328 test_selector_in_html(
":nth-child(even)", seven_ps,
329 pset([
2,
4,
6]), pset([
1,
3,
5,
7]));
330 test_selector_in_html(
":nth-child(2n-1)", seven_ps,
331 pset([
1,
3,
5,
7]), pset([
2,
4,
6]));
332 test_selector_in_html(
":nth-child( 2n - 1 )", seven_ps,
333 pset([
1,
3,
5,
7]), pset([
2,
4,
6]));
334 test_selector_in_html(
":nth-child(2n+1)", seven_ps,
335 pset([
1,
3,
5,
7]), pset([
2,
4,
6]));
336 test_selector_in_html(
":nth-child( 2n + 1 )", seven_ps,
337 pset([
1,
3,
5,
7]), pset([
2,
4,
6]));
338 test_selector_in_html(
":nth-child(2n+0)", seven_ps,
339 pset([
2,
4,
6]), pset([
1,
3,
5,
7]));
340 test_selector_in_html(
":nth-child(2n-0)", seven_ps,
341 pset([
2,
4,
6]), pset([
1,
3,
5,
7]));
342 test_selector_in_html(
":nth-child(-n+3)", seven_ps,
343 pset([
1,
2,
3]), pset([
4,
5,
6,
7]));
344 test_selector_in_html(
":nth-child(-n-3)", seven_ps,
345 pset([]), pset([
1,
2,
3,
4,
5,
6,
7]));
346 test_selector_in_html(
":nth-child(n)", seven_ps,
347 pset([
1,
2,
3,
4,
5,
6,
7]), pset([]));
348 test_selector_in_html(
":nth-child(n-3)", seven_ps,
349 pset([
1,
2,
3,
4,
5,
6,
7]), pset([]));
350 test_selector_in_html(
":nth-child(n+3)", seven_ps,
351 pset([
3,
4,
5,
6,
7]), pset([
1,
2]));
352 test_selector_in_html(
":nth-child(2n+3)", seven_ps,
353 pset([
3,
5,
7]), pset([
1,
2,
4,
6]));
354 test_selector_in_html(
":nth-child(2n)", seven_ps,
355 pset([
2,
4,
6]), pset([
1,
3,
5,
7]));
356 test_selector_in_html(
":nth-child(2n-3)", seven_ps,
357 pset([
1,
3,
5,
7]), pset([
2,
4,
6]));
358 test_selector_in_html(
":nth-child(-1n+3)", seven_ps,
359 pset([
1,
2,
3]), pset([
4,
5,
6,
7]));
360 test_selector_in_html(
":nth-child(-2n+3)", seven_ps,
361 pset([
1,
3]), pset([
2,
4,
5,
6,
7]));
362 // And a few spot-checks for the other :nth-* selectors
363 test_selector_in_html(
":nth-child(4n+1)", seven_ps,
364 pset([
1,
5]), pset([
2,
3,
4,
6,
7]));
365 test_selector_in_html(
":nth-last-child(4n+1)", seven_ps,
366 pset([
3,
7]), pset([
1,
2,
4,
5,
6]));
367 test_selector_in_html(
":nth-of-type(4n+1)", seven_ps,
368 pset([
1,
5]), pset([
2,
3,
4,
6,
7]));
369 test_selector_in_html(
":nth-last-of-type(4n+1)", seven_ps,
370 pset([
3,
7]), pset([
1,
2,
4,
5,
6]));
371 test_selector_in_html(
":nth-child(6)", seven_ps,
372 pset([
6]), pset([
1,
2,
3,
4,
5,
7]));
373 test_selector_in_html(
":nth-last-child(6)", seven_ps,
374 pset([
2]), pset([
1,
3,
4,
5,
6,
7]));
375 test_selector_in_html(
":nth-of-type(6)", seven_ps,
376 pset([
6]), pset([
1,
2,
3,
4,
5,
7]));
377 test_selector_in_html(
":nth-last-of-type(6)", seven_ps,
378 pset([
2]), pset([
1,
3,
4,
5,
6,
7]));
380 // Test [first|last|only]-[child|node|of-type]
381 var interesting_doc =
"<!----> <div id='p1'> <!---->x<p id='s1'></p> <!----><p id='s2'></p> <!----></div> <!----><p id='p2'> <!----><span id='s3'></span> <!----><span id='s4'></span> <!---->x</p> <!----><div id='p3'> <!----><p id='s5'></p> <!----></div> <!---->";
382 function idset(ids) { // takes an array of ids
383 return function idset_filter(doc) {
385 for each (var id in ids)
386 result.push(doc.getElementById(id));
390 test_parseable(
":first-child");
391 test_parseable(
":last-child");
392 test_parseable(
":only-child");
393 test_parseable(
":-moz-first-node");
394 test_parseable(
":-moz-last-node");
395 test_parseable(
":first-of-type");
396 test_parseable(
":last-of-type");
397 test_parseable(
":only-of-type");
398 test_selector_in_html(
":first-child", seven_ps,
399 pset([
1]), pset([
2,
3,
4,
5,
6,
7]));
400 test_selector_in_html(
":first-child", interesting_doc,
401 idset([
"p1",
"s1",
"s3",
"s5"]),
402 idset([
"s2",
"p2",
"s4",
"p3"]));
403 test_selector_in_html(
":-moz-first-node", interesting_doc,
404 idset([
"p1",
"s3",
"s5"]),
405 idset([
"s1",
"s2",
"p2",
"s4",
"p3"]));
406 test_selector_in_html(
":last-child", seven_ps,
407 pset([
7]), pset([
1,
2,
3,
4,
5,
6]));
408 test_selector_in_html(
":last-child", interesting_doc,
409 idset([
"s2",
"s4",
"p3",
"s5"]),
410 idset([
"p1",
"s1",
"p2",
"s3"]));
411 test_selector_in_html(
":-moz-last-node", interesting_doc,
412 idset([
"s2",
"p3",
"s5"]),
413 idset([
"p1",
"s1",
"p2",
"s3",
"s4"]));
414 test_selector_in_html(
":only-child", seven_ps,
415 pset([]), pset([
1,
2,
3,
4,
5,
6,
7]));
416 test_selector_in_html(
":only-child", interesting_doc,
418 idset([
"p1",
"s1",
"s2",
"p2",
"s3",
"s4",
"p3"]));
419 test_selector_in_html(
":first-of-type", seven_ps,
420 pset([
1]), pset([
2,
3,
4,
5,
6,
7]));
421 test_selector_in_html(
":first-of-type", interesting_doc,
422 idset([
"p1",
"s1",
"p2",
"s3",
"s5"]),
423 idset([
"s2",
"s4",
"p3"]));
424 test_selector_in_html(
":last-of-type", seven_ps,
425 pset([
7]), pset([
1,
2,
3,
4,
5,
6]));
426 test_selector_in_html(
":last-of-type", interesting_doc,
427 idset([
"s2",
"p2",
"s4",
"p3",
"s5"]),
428 idset([
"p1",
"s1",
"s3"]));
429 test_selector_in_html(
":only-of-type", seven_ps,
430 pset([]), pset([
1,
2,
3,
4,
5,
6,
7]));
431 test_selector_in_html(
":only-of-type", interesting_doc,
433 idset([
"p1",
"s1",
"s2",
"s3",
"s4",
"p3"]));
435 // And a bunch of tests for the of-type aspect of :nth-of-type() and
436 // :nth-last-of-type(). Note that the last div here contains two
438 var
mixed_elements=
"<p></p><p></p><div></div><p></p><div><p></p><address></address></div><address></address>";
439 function pdaset(ps, divs, addresses) { // takes an array of
1-based indices
440 var l = { p: ps, div: divs, address: addresses };
441 return function pdaset_filter(doc) {
444 var a = doc.getElementsByTagName(tag);
445 var indices = l[tag];
446 for (var i in indices)
447 result.push(a[indices[i] -
1]);
452 test_selector_in_html(
":nth-of-type(odd)", mixed_elements,
453 pdaset([
1,
3,
4], [
1], [
1,
2]),
454 pdaset([
2], [
2], []));
455 test_selector_in_html(
":nth-of-type(2n-0)", mixed_elements,
456 pdaset([
2], [
2], []),
457 pdaset([
1,
3,
4], [
1], [
1,
2]));
458 test_selector_in_html(
":nth-last-of-type(even)", mixed_elements,
459 pdaset([
2], [
1], []),
460 pdaset([
1,
3,
4], [
2], [
1,
2]));