assaf-scrapi 1.2.1

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