Froze rails gems
[depot.git] / vendor / rails / actionpack / test / controller / selector_test.rb
1 #--
2 # Copyright (c) 2006 Assaf Arkin (http://labnotes.org)
3 # Under MIT and/or CC By license.
4 #++
5
6 require 'abstract_unit'
7 require 'controller/fake_controllers'
8
9 class SelectorTest < Test::Unit::TestCase
10 #
11 # Basic selector: element, id, class, attributes.
12 #
13
14 def test_element
15 parse(%Q{<div id="1"></div><p></p><div id="2"></div>})
16 # Match element by name.
17 select("div")
18 assert_equal 2, @matches.size
19 assert_equal "1", @matches[0].attributes["id"]
20 assert_equal "2", @matches[1].attributes["id"]
21 # Not case sensitive.
22 select("DIV")
23 assert_equal 2, @matches.size
24 assert_equal "1", @matches[0].attributes["id"]
25 assert_equal "2", @matches[1].attributes["id"]
26 # Universal match (all elements).
27 select("*")
28 assert_equal 3, @matches.size
29 assert_equal "1", @matches[0].attributes["id"]
30 assert_equal nil, @matches[1].attributes["id"]
31 assert_equal "2", @matches[2].attributes["id"]
32 end
33
34
35 def test_identifier
36 parse(%Q{<div id="1"></div><p></p><div id="2"></div>})
37 # Match element by ID.
38 select("div#1")
39 assert_equal 1, @matches.size
40 assert_equal "1", @matches[0].attributes["id"]
41 # Match element by ID, substitute value.
42 select("div#?", 2)
43 assert_equal 1, @matches.size
44 assert_equal "2", @matches[0].attributes["id"]
45 # Element name does not match ID.
46 select("p#?", 2)
47 assert_equal 0, @matches.size
48 # Use regular expression.
49 select("#?", /\d/)
50 assert_equal 2, @matches.size
51 end
52
53
54 def test_class_name
55 parse(%Q{<div id="1" class=" foo "></div><p id="2" class=" foo bar "></p><div id="3" class="bar"></div>})
56 # Match element with specified class.
57 select("div.foo")
58 assert_equal 1, @matches.size
59 assert_equal "1", @matches[0].attributes["id"]
60 # Match any element with specified class.
61 select("*.foo")
62 assert_equal 2, @matches.size
63 assert_equal "1", @matches[0].attributes["id"]
64 assert_equal "2", @matches[1].attributes["id"]
65 # Match elements with other class.
66 select("*.bar")
67 assert_equal 2, @matches.size
68 assert_equal "2", @matches[0].attributes["id"]
69 assert_equal "3", @matches[1].attributes["id"]
70 # Match only element with both class names.
71 select("*.bar.foo")
72 assert_equal 1, @matches.size
73 assert_equal "2", @matches[0].attributes["id"]
74 end
75
76
77 def test_attribute
78 parse(%Q{<div id="1"></div><p id="2" title="" bar="foo"></p><div id="3" title="foo"></div>})
79 # Match element with attribute.
80 select("div[title]")
81 assert_equal 1, @matches.size
82 assert_equal "3", @matches[0].attributes["id"]
83 # Match any element with attribute.
84 select("*[title]")
85 assert_equal 2, @matches.size
86 assert_equal "2", @matches[0].attributes["id"]
87 assert_equal "3", @matches[1].attributes["id"]
88 # Match element with attribute value.
89 select("*[title=foo]")
90 assert_equal 1, @matches.size
91 assert_equal "3", @matches[0].attributes["id"]
92 # Match element with attribute and attribute value.
93 select("[bar=foo][title]")
94 assert_equal 1, @matches.size
95 assert_equal "2", @matches[0].attributes["id"]
96 # Not case sensitive.
97 select("[BAR=foo][TiTle]")
98 assert_equal 1, @matches.size
99 assert_equal "2", @matches[0].attributes["id"]
100 end
101
102
103 def test_attribute_quoted
104 parse(%Q{<div id="1" title="foo"></div><div id="2" title="bar"></div><div id="3" title=" bar "></div>})
105 # Match without quotes.
106 select("[title = bar]")
107 assert_equal 1, @matches.size
108 assert_equal "2", @matches[0].attributes["id"]
109 # Match with single quotes.
110 select("[title = 'bar' ]")
111 assert_equal 1, @matches.size
112 assert_equal "2", @matches[0].attributes["id"]
113 # Match with double quotes.
114 select("[title = \"bar\" ]")
115 assert_equal 1, @matches.size
116 assert_equal "2", @matches[0].attributes["id"]
117 # Match with spaces.
118 select("[title = \" bar \" ]")
119 assert_equal 1, @matches.size
120 assert_equal "3", @matches[0].attributes["id"]
121 end
122
123
124 def test_attribute_equality
125 parse(%Q{<div id="1" title="foo bar"></div><div id="2" title="barbaz"></div>})
126 # Match (fail) complete value.
127 select("[title=bar]")
128 assert_equal 0, @matches.size
129 # Match space-separate word.
130 select("[title~=foo]")
131 assert_equal 1, @matches.size
132 assert_equal "1", @matches[0].attributes["id"]
133 select("[title~=bar]")
134 assert_equal 1, @matches.size
135 assert_equal "1", @matches[0].attributes["id"]
136 # Match beginning of value.
137 select("[title^=ba]")
138 assert_equal 1, @matches.size
139 assert_equal "2", @matches[0].attributes["id"]
140 # Match end of value.
141 select("[title$=ar]")
142 assert_equal 1, @matches.size
143 assert_equal "1", @matches[0].attributes["id"]
144 # Match text in value.
145 select("[title*=bar]")
146 assert_equal 2, @matches.size
147 assert_equal "1", @matches[0].attributes["id"]
148 assert_equal "2", @matches[1].attributes["id"]
149 # Match first space separated word.
150 select("[title|=foo]")
151 assert_equal 1, @matches.size
152 assert_equal "1", @matches[0].attributes["id"]
153 select("[title|=bar]")
154 assert_equal 0, @matches.size
155 end
156
157
158 #
159 # Selector composition: groups, sibling, children
160 #
161
162
163 def test_selector_group
164 parse(%Q{<h1 id="1"></h1><h2 id="2"></h2><h3 id="3"></h3>})
165 # Simple group selector.
166 select("h1,h3")
167 assert_equal 2, @matches.size
168 assert_equal "1", @matches[0].attributes["id"]
169 assert_equal "3", @matches[1].attributes["id"]
170 select("h1 , h3")
171 assert_equal 2, @matches.size
172 assert_equal "1", @matches[0].attributes["id"]
173 assert_equal "3", @matches[1].attributes["id"]
174 # Complex group selector.
175 parse(%Q{<h1 id="1"><a href="foo"></a></h1><h2 id="2"><a href="bar"></a></h2><h3 id="2"><a href="baz"></a></h3>})
176 select("h1 a, h3 a")
177 assert_equal 2, @matches.size
178 assert_equal "foo", @matches[0].attributes["href"]
179 assert_equal "baz", @matches[1].attributes["href"]
180 # And now for the three selector challenge.
181 parse(%Q{<h1 id="1"><a href="foo"></a></h1><h2 id="2"><a href="bar"></a></h2><h3 id="2"><a href="baz"></a></h3>})
182 select("h1 a, h2 a, h3 a")
183 assert_equal 3, @matches.size
184 assert_equal "foo", @matches[0].attributes["href"]
185 assert_equal "bar", @matches[1].attributes["href"]
186 assert_equal "baz", @matches[2].attributes["href"]
187 end
188
189
190 def test_sibling_selector
191 parse(%Q{<h1 id="1"></h1><h2 id="2"></h2><h3 id="3"></h3>})
192 # Test next sibling.
193 select("h1+*")
194 assert_equal 1, @matches.size
195 assert_equal "2", @matches[0].attributes["id"]
196 select("h1+h2")
197 assert_equal 1, @matches.size
198 assert_equal "2", @matches[0].attributes["id"]
199 select("h1+h3")
200 assert_equal 0, @matches.size
201 select("*+h3")
202 assert_equal 1, @matches.size
203 assert_equal "3", @matches[0].attributes["id"]
204 # Test any sibling.
205 select("h1~*")
206 assert_equal 2, @matches.size
207 assert_equal "2", @matches[0].attributes["id"]
208 assert_equal "3", @matches[1].attributes["id"]
209 select("h2~*")
210 assert_equal 1, @matches.size
211 assert_equal "3", @matches[0].attributes["id"]
212 end
213
214
215 def test_children_selector
216 parse(%Q{<div><p id="1"><span id="2"></span></p></div><div><p id="3"><span id="4" class="foo"></span></p></div>})
217 # Test child selector.
218 select("div>p")
219 assert_equal 2, @matches.size
220 assert_equal "1", @matches[0].attributes["id"]
221 assert_equal "3", @matches[1].attributes["id"]
222 select("div>span")
223 assert_equal 0, @matches.size
224 select("div>p#3")
225 assert_equal 1, @matches.size
226 assert_equal "3", @matches[0].attributes["id"]
227 select("div>p>span")
228 assert_equal 2, @matches.size
229 assert_equal "2", @matches[0].attributes["id"]
230 assert_equal "4", @matches[1].attributes["id"]
231 # Test descendant selector.
232 select("div p")
233 assert_equal 2, @matches.size
234 assert_equal "1", @matches[0].attributes["id"]
235 assert_equal "3", @matches[1].attributes["id"]
236 select("div span")
237 assert_equal 2, @matches.size
238 assert_equal "2", @matches[0].attributes["id"]
239 assert_equal "4", @matches[1].attributes["id"]
240 select("div *#3")
241 assert_equal 1, @matches.size
242 assert_equal "3", @matches[0].attributes["id"]
243 select("div *#4")
244 assert_equal 1, @matches.size
245 assert_equal "4", @matches[0].attributes["id"]
246 # This is here because it failed before when whitespaces
247 # were not properly stripped.
248 select("div .foo")
249 assert_equal 1, @matches.size
250 assert_equal "4", @matches[0].attributes["id"]
251 end
252
253
254 #
255 # Pseudo selectors: root, nth-child, empty, content, etc
256 #
257
258
259 def test_root_selector
260 parse(%Q{<div id="1"><div id="2"></div></div>})
261 # Can only find element if it's root.
262 select(":root")
263 assert_equal 1, @matches.size
264 assert_equal "1", @matches[0].attributes["id"]
265 select("#1:root")
266 assert_equal 1, @matches.size
267 assert_equal "1", @matches[0].attributes["id"]
268 select("#2:root")
269 assert_equal 0, @matches.size
270 # Opposite for nth-child.
271 select("#1:nth-child(1)")
272 assert_equal 0, @matches.size
273 end
274
275
276 def test_nth_child_odd_even
277 parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
278 # Test odd nth children.
279 select("tr:nth-child(odd)")
280 assert_equal 2, @matches.size
281 assert_equal "1", @matches[0].attributes["id"]
282 assert_equal "3", @matches[1].attributes["id"]
283 # Test even nth children.
284 select("tr:nth-child(even)")
285 assert_equal 2, @matches.size
286 assert_equal "2", @matches[0].attributes["id"]
287 assert_equal "4", @matches[1].attributes["id"]
288 end
289
290
291 def test_nth_child_a_is_zero
292 parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
293 # Test the third child.
294 select("tr:nth-child(0n+3)")
295 assert_equal 1, @matches.size
296 assert_equal "3", @matches[0].attributes["id"]
297 # Same but an can be omitted when zero.
298 select("tr:nth-child(3)")
299 assert_equal 1, @matches.size
300 assert_equal "3", @matches[0].attributes["id"]
301 # Second element (but not every second element).
302 select("tr:nth-child(0n+2)")
303 assert_equal 1, @matches.size
304 assert_equal "2", @matches[0].attributes["id"]
305 # Before first and past last returns nothing.:
306 assert_raises(ArgumentError) { select("tr:nth-child(-1)") }
307 select("tr:nth-child(0)")
308 assert_equal 0, @matches.size
309 select("tr:nth-child(5)")
310 assert_equal 0, @matches.size
311 end
312
313
314 def test_nth_child_a_is_one
315 parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
316 # a is group of one, pick every element in group.
317 select("tr:nth-child(1n+0)")
318 assert_equal 4, @matches.size
319 # Same but a can be omitted when one.
320 select("tr:nth-child(n+0)")
321 assert_equal 4, @matches.size
322 # Same but b can be omitted when zero.
323 select("tr:nth-child(n)")
324 assert_equal 4, @matches.size
325 end
326
327
328 def test_nth_child_b_is_zero
329 parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
330 # If b is zero, pick the n-th element (here each one).
331 select("tr:nth-child(n+0)")
332 assert_equal 4, @matches.size
333 # If b is zero, pick the n-th element (here every second).
334 select("tr:nth-child(2n+0)")
335 assert_equal 2, @matches.size
336 assert_equal "1", @matches[0].attributes["id"]
337 assert_equal "3", @matches[1].attributes["id"]
338 # If a and b are both zero, no element selected.
339 select("tr:nth-child(0n+0)")
340 assert_equal 0, @matches.size
341 select("tr:nth-child(0)")
342 assert_equal 0, @matches.size
343 end
344
345
346 def test_nth_child_a_is_negative
347 parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
348 # Since a is -1, picks the first three elements.
349 select("tr:nth-child(-n+3)")
350 assert_equal 3, @matches.size
351 assert_equal "1", @matches[0].attributes["id"]
352 assert_equal "2", @matches[1].attributes["id"]
353 assert_equal "3", @matches[2].attributes["id"]
354 # Since a is -2, picks the first in every second of first four elements.
355 select("tr:nth-child(-2n+3)")
356 assert_equal 2, @matches.size
357 assert_equal "1", @matches[0].attributes["id"]
358 assert_equal "3", @matches[1].attributes["id"]
359 # Since a is -2, picks the first in every second of first three elements.
360 select("tr:nth-child(-2n+2)")
361 assert_equal 1, @matches.size
362 assert_equal "1", @matches[0].attributes["id"]
363 end
364
365
366 def test_nth_child_b_is_negative
367 parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
368 # Select last of four.
369 select("tr:nth-child(4n-1)")
370 assert_equal 1, @matches.size
371 assert_equal "4", @matches[0].attributes["id"]
372 # Select first of four.
373 select("tr:nth-child(4n-4)")
374 assert_equal 1, @matches.size
375 assert_equal "1", @matches[0].attributes["id"]
376 # Select last of every second.
377 select("tr:nth-child(2n-1)")
378 assert_equal 2, @matches.size
379 assert_equal "2", @matches[0].attributes["id"]
380 assert_equal "4", @matches[1].attributes["id"]
381 # Select nothing since an+b always < 0
382 select("tr:nth-child(-1n-1)")
383 assert_equal 0, @matches.size
384 end
385
386
387 def test_nth_child_substitution_values
388 parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
389 # Test with ?n?.
390 select("tr:nth-child(?n?)", 2, 1)
391 assert_equal 2, @matches.size
392 assert_equal "1", @matches[0].attributes["id"]
393 assert_equal "3", @matches[1].attributes["id"]
394 select("tr:nth-child(?n?)", 2, 2)
395 assert_equal 2, @matches.size
396 assert_equal "2", @matches[0].attributes["id"]
397 assert_equal "4", @matches[1].attributes["id"]
398 select("tr:nth-child(?n?)", 4, 2)
399 assert_equal 1, @matches.size
400 assert_equal "2", @matches[0].attributes["id"]
401 # Test with ? (b only).
402 select("tr:nth-child(?)", 3)
403 assert_equal 1, @matches.size
404 assert_equal "3", @matches[0].attributes["id"]
405 select("tr:nth-child(?)", 5)
406 assert_equal 0, @matches.size
407 end
408
409
410 def test_nth_last_child
411 parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
412 # Last two elements.
413 select("tr:nth-last-child(-n+2)")
414 assert_equal 2, @matches.size
415 assert_equal "3", @matches[0].attributes["id"]
416 assert_equal "4", @matches[1].attributes["id"]
417 # All old elements counting from last one.
418 select("tr:nth-last-child(odd)")
419 assert_equal 2, @matches.size
420 assert_equal "2", @matches[0].attributes["id"]
421 assert_equal "4", @matches[1].attributes["id"]
422 end
423
424
425 def test_nth_of_type
426 parse(%Q{<table><thead></thead><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
427 # First two elements.
428 select("tr:nth-of-type(-n+2)")
429 assert_equal 2, @matches.size
430 assert_equal "1", @matches[0].attributes["id"]
431 assert_equal "2", @matches[1].attributes["id"]
432 # All old elements counting from last one.
433 select("tr:nth-last-of-type(odd)")
434 assert_equal 2, @matches.size
435 assert_equal "2", @matches[0].attributes["id"]
436 assert_equal "4", @matches[1].attributes["id"]
437 end
438
439
440 def test_first_and_last
441 parse(%Q{<table><thead></thead><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
442 # First child.
443 select("tr:first-child")
444 assert_equal 0, @matches.size
445 select(":first-child")
446 assert_equal 1, @matches.size
447 assert_equal "thead", @matches[0].name
448 # First of type.
449 select("tr:first-of-type")
450 assert_equal 1, @matches.size
451 assert_equal "1", @matches[0].attributes["id"]
452 select("thead:first-of-type")
453 assert_equal 1, @matches.size
454 assert_equal "thead", @matches[0].name
455 select("div:first-of-type")
456 assert_equal 0, @matches.size
457 # Last child.
458 select("tr:last-child")
459 assert_equal 1, @matches.size
460 assert_equal "4", @matches[0].attributes["id"]
461 # Last of type.
462 select("tr:last-of-type")
463 assert_equal 1, @matches.size
464 assert_equal "4", @matches[0].attributes["id"]
465 select("thead:last-of-type")
466 assert_equal 1, @matches.size
467 assert_equal "thead", @matches[0].name
468 select("div:last-of-type")
469 assert_equal 0, @matches.size
470 end
471
472
473 def test_first_and_last
474 # Only child.
475 parse(%Q{<table><tr></tr></table>})
476 select("table:only-child")
477 assert_equal 0, @matches.size
478 select("tr:only-child")
479 assert_equal 1, @matches.size
480 assert_equal "tr", @matches[0].name
481 parse(%Q{<table><tr></tr><tr></tr></table>})
482 select("tr:only-child")
483 assert_equal 0, @matches.size
484 # Only of type.
485 parse(%Q{<table><thead></thead><tr></tr><tr></tr></table>})
486 select("thead:only-of-type")
487 assert_equal 1, @matches.size
488 assert_equal "thead", @matches[0].name
489 select("td:only-of-type")
490 assert_equal 0, @matches.size
491 end
492
493
494 def test_empty
495 parse(%Q{<table><tr></tr></table>})
496 select("table:empty")
497 assert_equal 0, @matches.size
498 select("tr:empty")
499 assert_equal 1, @matches.size
500 parse(%Q{<div> </div>})
501 select("div:empty")
502 assert_equal 1, @matches.size
503 end
504
505
506 def test_content
507 parse(%Q{<div> </div>})
508 select("div:content()")
509 assert_equal 1, @matches.size
510 parse(%Q{<div>something </div>})
511 select("div:content()")
512 assert_equal 0, @matches.size
513 select("div:content(something)")
514 assert_equal 1, @matches.size
515 select("div:content( 'something' )")
516 assert_equal 1, @matches.size
517 select("div:content( \"something\" )")
518 assert_equal 1, @matches.size
519 select("div:content(?)", "something")
520 assert_equal 1, @matches.size
521 select("div:content(?)", /something/)
522 assert_equal 1, @matches.size
523 end
524
525
526 #
527 # Test negation.
528 #
529
530
531 def test_element_negation
532 parse(%Q{<p></p><div></div>})
533 select("*")
534 assert_equal 2, @matches.size
535 select("*:not(p)")
536 assert_equal 1, @matches.size
537 assert_equal "div", @matches[0].name
538 select("*:not(div)")
539 assert_equal 1, @matches.size
540 assert_equal "p", @matches[0].name
541 select("*:not(span)")
542 assert_equal 2, @matches.size
543 end
544
545
546 def test_id_negation
547 parse(%Q{<p id="1"></p><p id="2"></p>})
548 select("p")
549 assert_equal 2, @matches.size
550 select(":not(#1)")
551 assert_equal 1, @matches.size
552 assert_equal "2", @matches[0].attributes["id"]
553 select(":not(#2)")
554 assert_equal 1, @matches.size
555 assert_equal "1", @matches[0].attributes["id"]
556 end
557
558
559 def test_class_name_negation
560 parse(%Q{<p class="foo"></p><p class="bar"></p>})
561 select("p")
562 assert_equal 2, @matches.size
563 select(":not(.foo)")
564 assert_equal 1, @matches.size
565 assert_equal "bar", @matches[0].attributes["class"]
566 select(":not(.bar)")
567 assert_equal 1, @matches.size
568 assert_equal "foo", @matches[0].attributes["class"]
569 end
570
571
572 def test_attribute_negation
573 parse(%Q{<p title="foo"></p><p title="bar"></p>})
574 select("p")
575 assert_equal 2, @matches.size
576 select(":not([title=foo])")
577 assert_equal 1, @matches.size
578 assert_equal "bar", @matches[0].attributes["title"]
579 select(":not([title=bar])")
580 assert_equal 1, @matches.size
581 assert_equal "foo", @matches[0].attributes["title"]
582 end
583
584
585 def test_pseudo_class_negation
586 parse(%Q{<div><p id="1"></p><p id="2"></p></div>})
587 select("p")
588 assert_equal 2, @matches.size
589 select("p:not(:first-child)")
590 assert_equal 1, @matches.size
591 assert_equal "2", @matches[0].attributes["id"]
592 select("p:not(:nth-child(2))")
593 assert_equal 1, @matches.size
594 assert_equal "1", @matches[0].attributes["id"]
595 end
596
597
598 def test_negation_details
599 parse(%Q{<p id="1"></p><p id="2"></p><p id="3"></p>})
600 assert_raises(ArgumentError) { select(":not(") }
601 assert_raises(ArgumentError) { select(":not(:not())") }
602 select("p:not(#1):not(#3)")
603 assert_equal 1, @matches.size
604 assert_equal "2", @matches[0].attributes["id"]
605 end
606
607
608 def test_select_from_element
609 parse(%Q{<div><p id="1"></p><p id="2"></p></div>})
610 select("div")
611 @matches = @matches[0].select("p")
612 assert_equal 2, @matches.size
613 assert_equal "1", @matches[0].attributes["id"]
614 assert_equal "2", @matches[1].attributes["id"]
615 end
616
617
618 protected
619
620 def parse(html)
621 @html = HTML::Document.new(html).root
622 end
623
624 def select(*selector)
625 @matches = HTML.selector(*selector).select(@html)
626 end
627
628 end