actionpack 1.12.5 → 1.13.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (179) hide show
  1. data/CHANGELOG +517 -15
  2. data/MIT-LICENSE +1 -1
  3. data/README +18 -20
  4. data/Rakefile +7 -4
  5. data/examples/address_book_controller.rb +3 -3
  6. data/examples/blog_controller.cgi +3 -3
  7. data/examples/debate_controller.cgi +5 -5
  8. data/lib/action_controller.rb +2 -2
  9. data/lib/action_controller/assertions.rb +73 -311
  10. data/lib/action_controller/{deprecated_assertions.rb → assertions/deprecated_assertions.rb} +32 -8
  11. data/lib/action_controller/assertions/dom_assertions.rb +25 -0
  12. data/lib/action_controller/assertions/model_assertions.rb +12 -0
  13. data/lib/action_controller/assertions/response_assertions.rb +140 -0
  14. data/lib/action_controller/assertions/routing_assertions.rb +82 -0
  15. data/lib/action_controller/assertions/selector_assertions.rb +571 -0
  16. data/lib/action_controller/assertions/tag_assertions.rb +117 -0
  17. data/lib/action_controller/base.rb +334 -163
  18. data/lib/action_controller/benchmarking.rb +3 -6
  19. data/lib/action_controller/caching.rb +83 -22
  20. data/lib/action_controller/cgi_ext/cgi_ext.rb +0 -7
  21. data/lib/action_controller/cgi_ext/cgi_methods.rb +167 -173
  22. data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +43 -22
  23. data/lib/action_controller/cgi_process.rb +50 -27
  24. data/lib/action_controller/components.rb +21 -25
  25. data/lib/action_controller/cookies.rb +10 -9
  26. data/lib/action_controller/{dependencies.rb → deprecated_dependencies.rb} +9 -27
  27. data/lib/action_controller/filters.rb +448 -225
  28. data/lib/action_controller/flash.rb +24 -20
  29. data/lib/action_controller/helpers.rb +2 -5
  30. data/lib/action_controller/integration.rb +40 -16
  31. data/lib/action_controller/layout.rb +11 -8
  32. data/lib/action_controller/macros/auto_complete.rb +3 -2
  33. data/lib/action_controller/macros/in_place_editing.rb +3 -2
  34. data/lib/action_controller/mime_responds.rb +41 -29
  35. data/lib/action_controller/mime_type.rb +68 -10
  36. data/lib/action_controller/pagination.rb +4 -3
  37. data/lib/action_controller/request.rb +22 -14
  38. data/lib/action_controller/rescue.rb +25 -22
  39. data/lib/action_controller/resources.rb +302 -0
  40. data/lib/action_controller/response.rb +20 -2
  41. data/lib/action_controller/response.rb.rej +17 -0
  42. data/lib/action_controller/routing.rb +1165 -567
  43. data/lib/action_controller/scaffolding.rb +30 -31
  44. data/lib/action_controller/session/active_record_store.rb +2 -0
  45. data/lib/action_controller/session/drb_store.rb +4 -0
  46. data/lib/action_controller/session/mem_cache_store.rb +4 -0
  47. data/lib/action_controller/session_management.rb +6 -9
  48. data/lib/action_controller/status_codes.rb +89 -0
  49. data/lib/action_controller/streaming.rb +6 -15
  50. data/lib/action_controller/templates/rescues/_request_and_response.rhtml +5 -5
  51. data/lib/action_controller/templates/rescues/diagnostics.rhtml +2 -2
  52. data/lib/action_controller/templates/rescues/routing_error.rhtml +4 -4
  53. data/lib/action_controller/templates/rescues/template_error.rhtml +1 -1
  54. data/lib/action_controller/templates/scaffolds/list.rhtml +1 -1
  55. data/lib/action_controller/test_process.rb +52 -30
  56. data/lib/action_controller/url_rewriter.rb +63 -29
  57. data/lib/action_controller/vendor/html-scanner/html/document.rb +1 -0
  58. data/lib/action_controller/vendor/html-scanner/html/node.rb +3 -4
  59. data/lib/action_controller/vendor/html-scanner/html/selector.rb +822 -0
  60. data/lib/action_controller/verification.rb +22 -11
  61. data/lib/action_pack.rb +1 -1
  62. data/lib/action_pack/version.rb +2 -2
  63. data/lib/action_view.rb +1 -1
  64. data/lib/action_view/base.rb +46 -43
  65. data/lib/action_view/compiled_templates.rb +1 -1
  66. data/lib/action_view/helpers/active_record_helper.rb +54 -17
  67. data/lib/action_view/helpers/asset_tag_helper.rb +97 -46
  68. data/lib/action_view/helpers/capture_helper.rb +1 -1
  69. data/lib/action_view/helpers/date_helper.rb +258 -136
  70. data/lib/action_view/helpers/debug_helper.rb +1 -1
  71. data/lib/action_view/helpers/deprecated_helper.rb +34 -0
  72. data/lib/action_view/helpers/form_helper.rb +75 -35
  73. data/lib/action_view/helpers/form_options_helper.rb +7 -5
  74. data/lib/action_view/helpers/form_tag_helper.rb +44 -6
  75. data/lib/action_view/helpers/java_script_macros_helper.rb +59 -46
  76. data/lib/action_view/helpers/javascript_helper.rb +71 -10
  77. data/lib/action_view/helpers/javascripts/controls.js +41 -23
  78. data/lib/action_view/helpers/javascripts/dragdrop.js +105 -76
  79. data/lib/action_view/helpers/javascripts/effects.js +293 -163
  80. data/lib/action_view/helpers/javascripts/prototype.js +897 -389
  81. data/lib/action_view/helpers/javascripts/prototype.js.rej +561 -0
  82. data/lib/action_view/helpers/number_helper.rb +111 -65
  83. data/lib/action_view/helpers/prototype_helper.rb +84 -109
  84. data/lib/action_view/helpers/scriptaculous_helper.rb +5 -0
  85. data/lib/action_view/helpers/tag_helper.rb +69 -16
  86. data/lib/action_view/helpers/text_helper.rb +149 -112
  87. data/lib/action_view/helpers/url_helper.rb +200 -107
  88. data/lib/action_view/template_error.rb +66 -42
  89. data/test/abstract_unit.rb +4 -2
  90. data/test/active_record_unit.rb +84 -56
  91. data/test/activerecord/active_record_assertions_test.rb +26 -18
  92. data/test/activerecord/active_record_store_test.rb +4 -36
  93. data/test/activerecord/pagination_test.rb +1 -6
  94. data/test/controller/action_pack_assertions_test.rb +230 -113
  95. data/test/controller/addresses_render_test.rb +2 -6
  96. data/test/controller/assert_select_test.rb +576 -0
  97. data/test/controller/base_test.rb +73 -3
  98. data/test/controller/caching_test.rb +228 -0
  99. data/test/controller/capture_test.rb +12 -10
  100. data/test/controller/cgi_test.rb +89 -12
  101. data/test/controller/components_test.rb +24 -2
  102. data/test/controller/content_type_test.rb +139 -0
  103. data/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb +0 -0
  104. data/test/controller/controller_fixtures/app/controllers/user_controller.rb +0 -0
  105. data/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb +0 -0
  106. data/test/controller/cookie_test.rb +33 -25
  107. data/test/controller/deprecated_instance_variables_test.rb +48 -0
  108. data/test/controller/deprecation/deprecated_base_methods_test.rb +60 -0
  109. data/test/controller/fake_controllers.rb +0 -1
  110. data/test/controller/filters_test.rb +301 -16
  111. data/test/controller/flash_test.rb +19 -2
  112. data/test/controller/helper_test.rb +2 -2
  113. data/test/controller/integration_test.rb +154 -0
  114. data/test/controller/layout_test.rb +115 -1
  115. data/test/controller/mime_responds_test.rb +94 -0
  116. data/test/controller/mime_type_test.rb +9 -0
  117. data/test/controller/new_render_test.rb +161 -11
  118. data/test/controller/raw_post_test.rb +52 -15
  119. data/test/controller/redirect_test.rb +27 -14
  120. data/test/controller/render_test.rb +76 -29
  121. data/test/controller/request_test.rb +55 -4
  122. data/test/controller/resources_test.rb +274 -0
  123. data/test/controller/routing_test.rb +1533 -824
  124. data/test/controller/selector_test.rb +628 -0
  125. data/test/controller/send_file_test.rb +9 -1
  126. data/test/controller/session_management_test.rb +51 -0
  127. data/test/controller/test_test.rb +113 -29
  128. data/test/controller/url_rewriter_test.rb +86 -17
  129. data/test/controller/verification_test.rb +19 -17
  130. data/test/controller/webservice_test.rb +0 -7
  131. data/test/fixtures/content_type/render_default_content_types_for_respond_to.rhtml +1 -0
  132. data/test/fixtures/content_type/render_default_for_rhtml.rhtml +1 -0
  133. data/test/fixtures/content_type/render_default_for_rjs.rjs +1 -0
  134. data/test/fixtures/content_type/render_default_for_rxml.rxml +1 -0
  135. data/test/fixtures/deprecated_instance_variables/_cookies_ivar.rhtml +1 -0
  136. data/test/fixtures/deprecated_instance_variables/_cookies_method.rhtml +1 -0
  137. data/test/fixtures/deprecated_instance_variables/_flash_ivar.rhtml +1 -0
  138. data/test/fixtures/deprecated_instance_variables/_flash_method.rhtml +1 -0
  139. data/test/fixtures/deprecated_instance_variables/_headers_ivar.rhtml +1 -0
  140. data/test/fixtures/deprecated_instance_variables/_headers_method.rhtml +1 -0
  141. data/test/fixtures/deprecated_instance_variables/_params_ivar.rhtml +1 -0
  142. data/test/fixtures/deprecated_instance_variables/_params_method.rhtml +1 -0
  143. data/test/fixtures/deprecated_instance_variables/_request_ivar.rhtml +1 -0
  144. data/test/fixtures/deprecated_instance_variables/_request_method.rhtml +1 -0
  145. data/test/fixtures/deprecated_instance_variables/_response_ivar.rhtml +1 -0
  146. data/test/fixtures/deprecated_instance_variables/_response_method.rhtml +1 -0
  147. data/test/fixtures/deprecated_instance_variables/_session_ivar.rhtml +1 -0
  148. data/test/fixtures/deprecated_instance_variables/_session_method.rhtml +1 -0
  149. data/test/fixtures/multipart/binary_file +0 -0
  150. data/test/fixtures/public/javascripts/application.js +1 -0
  151. data/test/fixtures/test/_hello.rxml +1 -0
  152. data/test/fixtures/test/hello_world_container.rxml +3 -0
  153. data/test/fixtures/topic.rb +2 -2
  154. data/test/template/active_record_helper_test.rb +83 -12
  155. data/test/template/asset_tag_helper_test.rb +75 -95
  156. data/test/template/compiled_templates_test.rb +1 -0
  157. data/test/template/date_helper_test.rb +873 -181
  158. data/test/template/deprecated_helper_test.rb +36 -0
  159. data/test/template/deprecated_instance_variables_test.rb +43 -0
  160. data/test/template/form_helper_test.rb +77 -1
  161. data/test/template/form_options_helper_test.rb +4 -0
  162. data/test/template/form_tag_helper_test.rb +66 -2
  163. data/test/template/java_script_macros_helper_test.rb +4 -1
  164. data/test/template/javascript_helper_test.rb +29 -0
  165. data/test/template/number_helper_test.rb +63 -27
  166. data/test/template/prototype_helper_test.rb +77 -34
  167. data/test/template/tag_helper_test.rb +34 -6
  168. data/test/template/text_helper_test.rb +69 -34
  169. data/test/template/url_helper_test.rb +168 -16
  170. data/test/testing_sandbox.rb +7 -22
  171. metadata +66 -20
  172. data/filler.txt +0 -50
  173. data/lib/action_controller/code_generation.rb +0 -235
  174. data/lib/action_controller/vendor/xml_simple.rb +0 -1019
  175. data/test/controller/caching_filestore.rb +0 -74
  176. data/test/fixtures/application_root/app/controllers/a_class_that_contains_a_controller/poorly_placed_controller.rb +0 -7
  177. data/test/fixtures/application_root/app/controllers/module_that_holds_controllers/nested_controller.rb +0 -3
  178. data/test/fixtures/application_root/app/models/a_class_that_contains_a_controller.rb +0 -7
  179. data/test/fixtures/dont_load.rb +0 -3
@@ -0,0 +1,628 @@
1
+ #--
2
+ # Copyright (c) 2006 Assaf Arkin (http://labnotes.org)
3
+ # Under MIT and/or CC By license.
4
+ #++
5
+
6
+ require File.dirname(__FILE__) + '/../abstract_unit'
7
+ require File.dirname(__FILE__) + '/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 alement with attribute value.
89
+ select("*[title=foo]")
90
+ assert_equal 1, @matches.size
91
+ assert_equal "3", @matches[0].attributes["id"]
92
+ # Match alement 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 challange.
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