scrapi 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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