xpath 2.0.0 → 3.2.0

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.
data/spec/xpath_spec.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  require 'nokogiri'
@@ -14,23 +16,23 @@ describe XPath do
14
16
  let(:template) { File.read(File.expand_path('fixtures/simple.html', File.dirname(__FILE__))) }
15
17
  let(:doc) { Nokogiri::HTML(template) }
16
18
 
17
- def xpath(type=nil, &block)
19
+ def xpath(type = nil, &block)
18
20
  doc.xpath XPath.generate(&block).to_xpath(type)
19
21
  end
20
22
 
21
- it "should work as a mixin" do
23
+ it 'should work as a mixin' do
22
24
  xpath = Thingy.new.foo_div.to_xpath
23
- doc.xpath(xpath).first[:title].should == 'fooDiv'
25
+ doc.xpath(xpath).first[:title].should eq 'fooDiv'
24
26
  end
25
27
 
26
28
  describe '#descendant' do
27
- it "should find nodes that are nested below the current node" do
29
+ it 'should find nodes that are nested below the current node' do
28
30
  @results = xpath { |x| x.descendant(:p) }
29
- @results[0].text.should == "Blah"
30
- @results[1].text.should == "Bax"
31
+ @results[0].text.should eq 'Blah'
32
+ @results[1].text.should eq 'Bax'
31
33
  end
32
34
 
33
- it "should not find nodes outside the context" do
35
+ it 'should not find nodes outside the context' do
34
36
  @results = xpath do |x|
35
37
  foo_div = x.descendant(:div).where(x.attr(:id) == 'foo')
36
38
  x.descendant(:p).where(x.attr(:id) == foo_div.attr(:title))
@@ -38,312 +40,523 @@ describe XPath do
38
40
  @results[0].should be_nil
39
41
  end
40
42
 
41
- it "should find multiple kinds of nodes" do
43
+ it 'should find multiple kinds of nodes' do
42
44
  @results = xpath { |x| x.descendant(:p, :ul) }
43
- @results[0].text.should == 'Blah'
44
- @results[3].text.should == 'A list'
45
+ @results[0].text.should eq 'Blah'
46
+ @results[3].text.should eq 'A list'
45
47
  end
46
48
 
47
- it "should find all nodes when no arguments given" do
49
+ it 'should find all nodes when no arguments given' do
48
50
  @results = xpath { |x| x.descendant[x.attr(:id) == 'foo'].descendant }
49
- @results[0].text.should == 'Blah'
50
- @results[4].text.should == 'A list'
51
+ @results[0].text.should eq 'Blah'
52
+ @results[4].text.should eq 'A list'
51
53
  end
52
54
  end
53
55
 
54
56
  describe '#child' do
55
- it "should find nodes that are nested directly below the current node" do
57
+ it 'should find nodes that are nested directly below the current node' do
56
58
  @results = xpath { |x| x.descendant(:div).child(:p) }
57
- @results[0].text.should == "Blah"
58
- @results[1].text.should == "Bax"
59
+ @results[0].text.should eq 'Blah'
60
+ @results[1].text.should eq 'Bax'
59
61
  end
60
62
 
61
- it "should not find nodes that are nested further down below the current node" do
63
+ it 'should not find nodes that are nested further down below the current node' do
62
64
  @results = xpath { |x| x.child(:p) }
63
65
  @results[0].should be_nil
64
66
  end
65
67
 
66
- it "should find multiple kinds of nodes" do
68
+ it 'should find multiple kinds of nodes' do
67
69
  @results = xpath { |x| x.descendant(:div).child(:p, :ul) }
68
- @results[0].text.should == 'Blah'
69
- @results[3].text.should == 'A list'
70
+ @results[0].text.should eq 'Blah'
71
+ @results[3].text.should eq 'A list'
70
72
  end
71
73
 
72
- it "should find all nodes when no arguments given" do
74
+ it 'should find all nodes when no arguments given' do
73
75
  @results = xpath { |x| x.descendant[x.attr(:id) == 'foo'].child }
74
- @results[0].text.should == 'Blah'
75
- @results[3].text.should == 'A list'
76
+ @results[0].text.should eq 'Blah'
77
+ @results[3].text.should eq 'A list'
76
78
  end
77
79
  end
78
80
 
79
81
  describe '#axis' do
80
- it "should find nodes given the xpath axis" do
82
+ it 'should find nodes given the xpath axis' do
81
83
  @results = xpath { |x| x.axis(:descendant, :p) }
82
- @results[0].text.should == "Blah"
84
+ @results[0].text.should eq 'Blah'
83
85
  end
84
86
 
85
- it "should find nodes given the xpath axis without a specific tag" do
87
+ it 'should find nodes given the xpath axis without a specific tag' do
86
88
  @results = xpath { |x| x.descendant(:div)[x.attr(:id) == 'foo'].axis(:descendant) }
87
- @results[0][:id].should == "fooDiv"
89
+ @results[0][:id].should eq 'fooDiv'
88
90
  end
89
91
  end
90
92
 
91
93
  describe '#next_sibling' do
92
- it "should find nodes which are immediate siblings of the current node" do
93
- xpath { |x| x.descendant(:p)[x.attr(:id) == 'fooDiv'].next_sibling(:p) }.first.text.should == 'Bax'
94
- xpath { |x| x.descendant(:p)[x.attr(:id) == 'fooDiv'].next_sibling(:ul, :p) }.first.text.should == 'Bax'
95
- xpath { |x| x.descendant(:p)[x.attr(:title) == 'monkey'].next_sibling(:ul, :p) }.first.text.should == 'A list'
94
+ it 'should find nodes which are immediate siblings of the current node' do
95
+ xpath { |x| x.descendant(:p)[x.attr(:id) == 'fooDiv'].next_sibling(:p) }.first.text.should eq 'Bax'
96
+ xpath { |x| x.descendant(:p)[x.attr(:id) == 'fooDiv'].next_sibling(:ul, :p) }.first.text.should eq 'Bax'
97
+ xpath { |x| x.descendant(:p)[x.attr(:title) == 'monkey'].next_sibling(:ul, :p) }.first.text.should eq 'A list'
96
98
  xpath { |x| x.descendant(:p)[x.attr(:id) == 'fooDiv'].next_sibling(:ul, :li) }.first.should be_nil
97
- xpath { |x| x.descendant(:p)[x.attr(:id) == 'fooDiv'].next_sibling }.first.text.should == 'Bax'
99
+ xpath { |x| x.descendant(:p)[x.attr(:id) == 'fooDiv'].next_sibling }.first.text.should eq 'Bax'
98
100
  end
99
101
  end
100
102
 
101
103
  describe '#previous_sibling' do
102
- it "should find nodes which are exactly preceding the current node" do
103
- xpath { |x| x.descendant(:p)[x.attr(:id) == 'wooDiv'].previous_sibling(:p) }.first.text.should == 'Bax'
104
- xpath { |x| x.descendant(:p)[x.attr(:id) == 'wooDiv'].previous_sibling(:ul, :p) }.first.text.should == 'Bax'
105
- xpath { |x| x.descendant(:p)[x.attr(:title) == 'gorilla'].previous_sibling(:ul, :p) }.first.text.should == 'A list'
104
+ it 'should find nodes which are exactly preceding the current node' do
105
+ xpath { |x| x.descendant(:p)[x.attr(:id) == 'wooDiv'].previous_sibling(:p) }.first.text.should eq 'Bax'
106
+ xpath { |x| x.descendant(:p)[x.attr(:id) == 'wooDiv'].previous_sibling(:ul, :p) }.first.text.should eq 'Bax'
107
+ xpath { |x| x.descendant(:p)[x.attr(:title) == 'gorilla'].previous_sibling(:ul, :p) }.first.text.should eq 'A list'
106
108
  xpath { |x| x.descendant(:p)[x.attr(:id) == 'wooDiv'].previous_sibling(:ul, :li) }.first.should be_nil
107
- xpath { |x| x.descendant(:p)[x.attr(:id) == 'wooDiv'].previous_sibling }.first.text.should == 'Bax'
109
+ xpath { |x| x.descendant(:p)[x.attr(:id) == 'wooDiv'].previous_sibling }.first.text.should eq 'Bax'
108
110
  end
109
111
  end
110
112
 
111
113
  describe '#anywhere' do
112
- it "should find nodes regardless of the context" do
114
+ it 'should find nodes regardless of the context' do
113
115
  @results = xpath do |x|
114
116
  foo_div = x.anywhere(:div).where(x.attr(:id) == 'foo')
115
117
  x.descendant(:p).where(x.attr(:id) == foo_div.attr(:title))
116
118
  end
117
- @results[0].text.should == "Blah"
119
+ @results[0].text.should eq 'Blah'
118
120
  end
119
121
 
120
- it "should find multiple kinds of nodes regardless of the context" do
122
+ it 'should find multiple kinds of nodes regardless of the context' do
121
123
  @results = xpath do |x|
122
- context=x.descendant(:div).where(x.attr(:id)=='woo')
124
+ context = x.descendant(:div).where(x.attr(:id) == 'woo')
123
125
  context.anywhere(:p, :ul)
124
126
  end
125
127
 
126
- @results[0].text.should == 'Blah'
127
- @results[3].text.should == 'A list'
128
- @results[4].text.should == 'A list'
129
- @results[6].text.should == 'Bax'
128
+ @results[0].text.should eq 'Blah'
129
+ @results[3].text.should eq 'A list'
130
+ @results[4].text.should eq 'A list'
131
+ @results[6].text.should eq 'Bax'
130
132
  end
131
133
 
132
- it "should find all nodes when no arguments given regardless of the context" do
134
+ it 'should find all nodes when no arguments given regardless of the context' do
133
135
  @results = xpath do |x|
134
- context=x.descendant(:div).where(x.attr(:id)=='woo')
136
+ context = x.descendant(:div).where(x.attr(:id) == 'woo')
135
137
  context.anywhere
136
138
  end
137
- @results[0].name.should == 'html'
138
- @results[1].name.should == 'head'
139
- @results[2].name.should == 'body'
140
- @results[6].text.should == 'Blah'
141
- @results[10].text.should == 'A list'
142
- @results[13].text.should == 'A list'
143
- @results[15].text.should == 'Bax'
139
+ @results[0].name.should eq 'html'
140
+ @results[1].name.should eq 'head'
141
+ @results[2].name.should eq 'body'
142
+ @results[6].text.should eq 'Blah'
143
+ @results[10].text.should eq 'A list'
144
+ @results[13].text.should eq 'A list'
145
+ @results[15].text.should eq 'Bax'
144
146
  end
145
-
146
147
  end
147
148
 
148
149
  describe '#contains' do
149
- it "should find nodes that contain the given string" do
150
+ it 'should find nodes that contain the given string' do
150
151
  @results = xpath do |x|
151
152
  x.descendant(:div).where(x.attr(:title).contains('ooD'))
152
153
  end
153
- @results[0][:id].should == "foo"
154
+ @results[0][:id].should eq 'foo'
154
155
  end
155
156
 
156
- it "should find nodes that contain the given expression" do
157
+ it 'should find nodes that contain the given expression' do
157
158
  @results = xpath do |x|
158
159
  expression = x.anywhere(:div).where(x.attr(:title) == 'fooDiv').attr(:id)
159
160
  x.descendant(:div).where(x.attr(:title).contains(expression))
160
161
  end
161
- @results[0][:id].should == "foo"
162
+ @results[0][:id].should eq 'foo'
163
+ end
164
+ end
165
+
166
+ describe '#contains_word' do
167
+ it 'should find nodes that contain the given word in its entirety' do
168
+ @results = xpath do |x|
169
+ x.descendant.where(x.attr(:class).contains_word('fish'))
170
+ end
171
+ @results[0].text.should eq 'Bax'
172
+ @results[1].text.should eq 'llama'
173
+ @results.length.should eq 2
162
174
  end
163
175
  end
164
176
 
165
177
  describe '#starts_with' do
166
- it "should find nodes that begin with the given string" do
178
+ it 'should find nodes that begin with the given string' do
167
179
  @results = xpath do |x|
168
180
  x.descendant(:*).where(x.attr(:id).starts_with('foo'))
169
181
  end
170
- @results.size.should == 2
171
- @results[0][:id].should == "foo"
172
- @results[1][:id].should == "fooDiv"
182
+ @results.size.should eq 2
183
+ @results[0][:id].should eq 'foo'
184
+ @results[1][:id].should eq 'fooDiv'
173
185
  end
174
186
 
175
- it "should find nodes that contain the given expression" do
187
+ it 'should find nodes that contain the given expression' do
176
188
  @results = xpath do |x|
177
189
  expression = x.anywhere(:div).where(x.attr(:title) == 'fooDiv').attr(:id)
178
190
  x.descendant(:div).where(x.attr(:title).starts_with(expression))
179
191
  end
180
- @results[0][:id].should == "foo"
192
+ @results[0][:id].should eq 'foo'
193
+ end
194
+ end
195
+
196
+ describe '#ends_with' do
197
+ it 'should find nodes that end with the given string' do
198
+ @results = xpath do |x|
199
+ x.descendant(:*).where(x.attr(:id).ends_with('oof'))
200
+ end
201
+ @results.size.should eq 2
202
+ @results[0][:id].should eq 'oof'
203
+ @results[1][:id].should eq 'viDoof'
204
+ end
205
+
206
+ it 'should find nodes that contain the given expression' do
207
+ @results = xpath do |x|
208
+ expression = x.anywhere(:div).where(x.attr(:title) == 'viDoof').attr(:id)
209
+ x.descendant(:div).where(x.attr(:title).ends_with(expression))
210
+ end
211
+ @results[0][:id].should eq 'oof'
212
+ end
213
+ end
214
+
215
+ describe '#uppercase' do
216
+ it 'should match uppercased text' do
217
+ @results = xpath do |x|
218
+ x.descendant(:div).where(x.attr(:title).uppercase == 'VIDOOF')
219
+ end
220
+ @results[0][:id].should eq 'oof'
221
+ end
222
+ end
223
+
224
+ describe '#lowercase' do
225
+ it 'should match lowercased text' do
226
+ @results = xpath do |x|
227
+ x.descendant(:div).where(x.attr(:title).lowercase == 'vidoof')
228
+ end
229
+ @results[0][:id].should eq 'oof'
181
230
  end
182
231
  end
183
232
 
184
233
  describe '#text' do
185
234
  it "should select a node's text" do
186
235
  @results = xpath { |x| x.descendant(:p).where(x.text == 'Bax') }
187
- @results[0].text.should == 'Bax'
188
- @results[1][:title].should == 'monkey'
236
+ @results[0].text.should eq 'Bax'
237
+ @results[1][:title].should eq 'monkey'
189
238
  @results = xpath { |x| x.descendant(:div).where(x.descendant(:p).text == 'Bax') }
190
- @results[0][:title].should == 'fooDiv'
239
+ @results[0][:title].should eq 'fooDiv'
240
+ end
241
+ end
242
+
243
+ describe '#substring' do
244
+ context 'when called with one argument' do
245
+ it 'should select the part of a string after the specified character' do
246
+ @results = xpath { |x| x.descendant(:span).where(x.attr(:id) == 'substring').text.substring(7) }
247
+ @results.should eq 'there'
248
+ end
249
+ end
250
+
251
+ context 'when called with two arguments' do
252
+ it 'should select the part of a string after the specified character, up to the given length' do
253
+ @results = xpath { |x| x.descendant(:span).where(x.attr(:id) == 'substring').text.substring(2, 4) }
254
+ @results.should eq 'ello'
255
+ end
256
+ end
257
+ end
258
+
259
+ describe '#function' do
260
+ it 'should call the given xpath function' do
261
+ @results = xpath { |x| x.function(:boolean, x.function(:true) == x.function(:false)) }
262
+ @results.should eq false
263
+ end
264
+ end
265
+
266
+ describe '#method' do
267
+ it 'should call the given xpath function with the current node as the first argument' do
268
+ @results = xpath { |x| x.descendant(:span).where(x.attr(:id) == 'string-length').text.method(:"string-length") }
269
+ @results.should eq 11
270
+ end
271
+ end
272
+
273
+ describe '#string_length' do
274
+ it 'should return the length of a string' do
275
+ @results = xpath { |x| x.descendant(:span).where(x.attr(:id) == 'string-length').text.string_length }
276
+ @results.should eq 11
191
277
  end
192
278
  end
193
279
 
194
280
  describe '#where' do
195
- it "should limit the expression to find only certain nodes" do
196
- xpath { |x| x.descendant(:div).where(:"@id = 'foo'") }.first[:title].should == "fooDiv"
281
+ it 'should limit the expression to find only certain nodes' do
282
+ xpath { |x| x.descendant(:div).where(:"@id = 'foo'") }.first[:title].should eq 'fooDiv'
197
283
  end
198
284
 
199
- it "should be aliased as []" do
200
- xpath { |x| x.descendant(:div)[:"@id = 'foo'"] }.first[:title].should == "fooDiv"
285
+ it 'should be aliased as []' do
286
+ xpath { |x| x.descendant(:div)[:"@id = 'foo'"] }.first[:title].should eq 'fooDiv'
287
+ end
288
+
289
+ it 'should be a no-op when nil condition is passed' do
290
+ XPath.descendant(:div).where(nil).to_s.should eq './/div'
201
291
  end
202
292
  end
203
293
 
204
294
  describe '#inverse' do
205
- it "should invert the expression" do
206
- xpath { |x| x.descendant(:p).where(x.attr(:id).equals('fooDiv').inverse) }.first.text.should == 'Bax'
295
+ it 'should invert the expression' do
296
+ xpath { |x| x.descendant(:p).where(x.attr(:id).equals('fooDiv').inverse) }.first.text.should eq 'Bax'
207
297
  end
208
298
 
209
- it "should be aliased as the unary tilde" do
210
- xpath { |x| x.descendant(:p).where(~x.attr(:id).equals('fooDiv')) }.first.text.should == 'Bax'
299
+ it 'should be aliased as the unary tilde' do
300
+ xpath { |x| x.descendant(:p).where(~x.attr(:id).equals('fooDiv')) }.first.text.should eq 'Bax'
301
+ end
302
+
303
+ it 'should be aliased as the unary bang' do
304
+ xpath { |x| x.descendant(:p).where(!x.attr(:id).equals('fooDiv')) }.first.text.should eq 'Bax'
211
305
  end
212
306
  end
213
307
 
214
308
  describe '#equals' do
215
- it "should limit the expression to find only certain nodes" do
216
- xpath { |x| x.descendant(:div).where(x.attr(:id).equals('foo')) }.first[:title].should == "fooDiv"
309
+ it 'should limit the expression to find only certain nodes' do
310
+ xpath { |x| x.descendant(:div).where(x.attr(:id).equals('foo')) }.first[:title].should eq 'fooDiv'
311
+ end
312
+
313
+ it 'should be aliased as ==' do
314
+ xpath { |x| x.descendant(:div).where(x.attr(:id) == 'foo') }.first[:title].should eq 'fooDiv'
315
+ end
316
+ end
317
+
318
+ describe '#not_equals' do
319
+ it 'should match only when not equal' do
320
+ xpath { |x| x.descendant(:div).where(x.attr(:id).not_equals('bar')) }.first[:title].should eq 'fooDiv'
217
321
  end
218
322
 
219
- it "should be aliased as ==" do
220
- xpath { |x| x.descendant(:div).where(x.attr(:id) == 'foo') }.first[:title].should == "fooDiv"
323
+ it 'should be aliased as !=' do
324
+ xpath { |x| x.descendant(:div).where(x.attr(:id) != 'bar') }.first[:title].should eq 'fooDiv'
221
325
  end
222
326
  end
223
327
 
224
328
  describe '#is' do
225
- it "uses equality when :exact given" do
226
- xpath(:exact) { |x| x.descendant(:div).where(x.attr(:id).is('foo')) }.first[:title].should == "fooDiv"
329
+ it 'uses equality when :exact given' do
330
+ xpath(:exact) { |x| x.descendant(:div).where(x.attr(:id).is('foo')) }.first[:title].should eq 'fooDiv'
227
331
  xpath(:exact) { |x| x.descendant(:div).where(x.attr(:id).is('oo')) }.first.should be_nil
228
332
  end
229
333
 
230
- it "uses substring matching otherwise" do
231
- xpath { |x| x.descendant(:div).where(x.attr(:id).is('foo')) }.first[:title].should == "fooDiv"
232
- xpath { |x| x.descendant(:div).where(x.attr(:id).is('oo')) }.first[:title].should == "fooDiv"
334
+ it 'uses substring matching otherwise' do
335
+ xpath { |x| x.descendant(:div).where(x.attr(:id).is('foo')) }.first[:title].should eq 'fooDiv'
336
+ xpath { |x| x.descendant(:div).where(x.attr(:id).is('oo')) }.first[:title].should eq 'fooDiv'
233
337
  end
234
338
  end
235
339
 
236
340
  describe '#one_of' do
237
- it "should return all nodes where the condition matches" do
341
+ it 'should return all nodes where the condition matches' do
238
342
  @results = xpath do |x|
239
343
  p = x.anywhere(:div).where(x.attr(:id) == 'foo').attr(:title)
240
344
  x.descendant(:*).where(x.attr(:id).one_of('foo', p, 'baz'))
241
345
  end
242
- @results[0][:title].should == "fooDiv"
243
- @results[1].text.should == "Blah"
244
- @results[2][:title].should == "bazDiv"
346
+ @results[0][:title].should eq 'fooDiv'
347
+ @results[1].text.should eq 'Blah'
348
+ @results[2][:title].should eq 'bazDiv'
245
349
  end
246
350
  end
247
351
 
248
352
  describe '#and' do
249
- it "should find all nodes in both expression" do
353
+ it 'should find all nodes in both expression' do
250
354
  @results = xpath do |x|
251
355
  x.descendant(:*).where(x.contains('Bax').and(x.attr(:title).equals('monkey')))
252
356
  end
253
- @results[0][:title].should == "monkey"
357
+ @results[0][:title].should eq 'monkey'
254
358
  end
255
359
 
256
- it "should be aliased as ampersand (&)" do
360
+ it 'should be aliased as ampersand (&)' do
257
361
  @results = xpath do |x|
258
362
  x.descendant(:*).where(x.contains('Bax') & x.attr(:title).equals('monkey'))
259
363
  end
260
- @results[0][:title].should == "monkey"
364
+ @results[0][:title].should eq 'monkey'
261
365
  end
262
366
  end
263
367
 
264
368
  describe '#or' do
265
- it "should find all nodes in either expression" do
369
+ it 'should find all nodes in either expression' do
266
370
  @results = xpath do |x|
267
371
  x.descendant(:*).where(x.attr(:id).equals('foo').or(x.attr(:id).equals('fooDiv')))
268
372
  end
269
- @results[0][:title].should == "fooDiv"
270
- @results[1].text.should == "Blah"
373
+ @results[0][:title].should eq 'fooDiv'
374
+ @results[1].text.should eq 'Blah'
271
375
  end
272
376
 
273
- it "should be aliased as pipe (|)" do
377
+ it 'should be aliased as pipe (|)' do
274
378
  @results = xpath do |x|
275
379
  x.descendant(:*).where(x.attr(:id).equals('foo') | x.attr(:id).equals('fooDiv'))
276
380
  end
277
- @results[0][:title].should == "fooDiv"
278
- @results[1].text.should == "Blah"
381
+ @results[0][:title].should eq 'fooDiv'
382
+ @results[1].text.should eq 'Blah'
279
383
  end
280
384
  end
281
385
 
282
386
  describe '#attr' do
283
- it "should be an attribute" do
387
+ it 'should be an attribute' do
284
388
  @results = xpath { |x| x.descendant(:div).where(x.attr(:id)) }
285
- @results[0][:title].should == "barDiv"
286
- @results[1][:title].should == "fooDiv"
389
+ @results[0][:title].should eq 'barDiv'
390
+ @results[1][:title].should eq 'fooDiv'
287
391
  end
288
392
  end
289
393
 
290
394
  describe '#css' do
291
- it "should find nodes by the given CSS selector" do
395
+ it 'should find nodes by the given CSS selector' do
292
396
  @results = xpath { |x| x.css('#preference p') }
293
- @results[0].text.should == 'allamas'
294
- @results[1].text.should == 'llama'
397
+ @results[0].text.should eq 'allamas'
398
+ @results[1].text.should eq 'llama'
295
399
  end
296
400
 
297
- it "should respect previous expression" do
401
+ it 'should respect previous expression' do
298
402
  @results = xpath { |x| x.descendant[x.attr(:id) == 'moar'].css('p') }
299
- @results[0].text.should == 'chimp'
300
- @results[1].text.should == 'flamingo'
403
+ @results[0].text.should eq 'chimp'
404
+ @results[1].text.should eq 'flamingo'
301
405
  end
302
406
 
303
- it "should be composable" do
407
+ it 'should be composable' do
304
408
  @results = xpath { |x| x.css('#moar').descendant(:p) }
305
- @results[0].text.should == 'chimp'
306
- @results[1].text.should == 'flamingo'
409
+ @results[0].text.should eq 'chimp'
410
+ @results[1].text.should eq 'flamingo'
307
411
  end
308
412
 
309
- it "should allow comma separated selectors" do
413
+ it 'should allow comma separated selectors' do
310
414
  @results = xpath { |x| x.descendant[x.attr(:id) == 'moar'].css('div, p') }
311
- @results[0].text.should == 'chimp'
312
- @results[1].text.should == 'elephant'
313
- @results[2].text.should == 'flamingo'
415
+ @results[0].text.should eq 'chimp'
416
+ @results[1].text.should eq 'elephant'
417
+ @results[2].text.should eq 'flamingo'
314
418
  end
315
419
  end
316
420
 
317
- describe '#name' do
421
+ describe '#qname' do
318
422
  it "should match the node's name" do
319
- xpath { |x| x.descendant(:*).where(x.name == 'ul') }.first.text.should == "A list"
423
+ xpath { |x| x.descendant(:*).where(x.qname == 'ul') }.first.text.should eq 'A list'
320
424
  end
321
425
  end
322
426
 
323
427
  describe '#union' do
324
- it "should create a union expression" do
428
+ it 'should create a union expression' do
325
429
  @expr1 = XPath.generate { |x| x.descendant(:p) }
326
430
  @expr2 = XPath.generate { |x| x.descendant(:div) }
327
431
  @collection = @expr1.union(@expr2)
328
432
  @xpath1 = @collection.where(XPath.attr(:id) == 'foo').to_xpath
329
433
  @xpath2 = @collection.where(XPath.attr(:id) == 'fooDiv').to_xpath
330
434
  @results = doc.xpath(@xpath1)
331
- @results[0][:title].should == 'fooDiv'
435
+ @results[0][:title].should eq 'fooDiv'
332
436
  @results = doc.xpath(@xpath2)
333
- @results[0][:id].should == 'fooDiv'
437
+ @results[0][:id].should eq 'fooDiv'
334
438
  end
335
439
 
336
- it "should be aliased as +" do
440
+ it 'should be aliased as +' do
337
441
  @expr1 = XPath.generate { |x| x.descendant(:p) }
338
442
  @expr2 = XPath.generate { |x| x.descendant(:div) }
339
443
  @collection = @expr1 + @expr2
340
444
  @xpath1 = @collection.where(XPath.attr(:id) == 'foo').to_xpath
341
445
  @xpath2 = @collection.where(XPath.attr(:id) == 'fooDiv').to_xpath
342
446
  @results = doc.xpath(@xpath1)
343
- @results[0][:title].should == 'fooDiv'
447
+ @results[0][:title].should eq 'fooDiv'
344
448
  @results = doc.xpath(@xpath2)
345
- @results[0][:id].should == 'fooDiv'
449
+ @results[0][:id].should eq 'fooDiv'
450
+ end
451
+ end
452
+
453
+ describe '#last' do
454
+ it 'returns the number of elements in the context' do
455
+ @results = xpath { |x| x.descendant(:p)[XPath.position() == XPath.last()] }
456
+ @results[0].text.should eq 'Bax'
457
+ @results[1].text.should eq 'Blah'
458
+ @results[2].text.should eq 'llama'
459
+ end
460
+ end
461
+
462
+ describe '#position' do
463
+ it 'returns the position of elements in the context' do
464
+ @results = xpath { |x| x.descendant(:p)[XPath.position() == 2] }
465
+ @results[0].text.should eq 'Bax'
466
+ @results[1].text.should eq 'Bax'
467
+ end
468
+ end
469
+
470
+ describe '#count' do
471
+ it 'counts the number of occurrences' do
472
+ @results = xpath { |x| x.descendant(:div)[x.descendant(:p).count == 2] }
473
+ @results[0][:id].should eq 'preference'
474
+ end
475
+ end
476
+
477
+ describe '#lte' do
478
+ it 'checks lesser than or equal' do
479
+ @results = xpath { |x| x.descendant(:p)[XPath.position() <= 2] }
480
+ @results[0].text.should eq 'Blah'
481
+ @results[1].text.should eq 'Bax'
482
+ @results[2][:title].should eq 'gorilla'
483
+ @results[3].text.should eq 'Bax'
484
+ end
485
+ end
486
+
487
+ describe '#lt' do
488
+ it 'checks lesser than' do
489
+ @results = xpath { |x| x.descendant(:p)[XPath.position() < 2] }
490
+ @results[0].text.should eq 'Blah'
491
+ @results[1][:title].should eq 'gorilla'
492
+ end
493
+ end
494
+
495
+ describe '#gte' do
496
+ it 'checks greater than or equal' do
497
+ @results = xpath { |x| x.descendant(:p)[XPath.position() >= 2] }
498
+ @results[0].text.should eq 'Bax'
499
+ @results[1][:title].should eq 'monkey'
500
+ @results[2].text.should eq 'Bax'
501
+ @results[3].text.should eq 'Blah'
502
+ end
503
+ end
504
+
505
+ describe '#gt' do
506
+ it 'checks greater than' do
507
+ @results = xpath { |x| x.descendant(:p)[XPath.position() > 2] }
508
+ @results[0][:title].should eq 'monkey'
509
+ @results[1].text.should eq 'Blah'
510
+ end
511
+ end
512
+
513
+ describe '#plus' do
514
+ it 'adds stuff' do
515
+ @results = xpath { |x| x.descendant(:p)[XPath.position().plus(1) == 2] }
516
+ @results[0][:id].should eq 'fooDiv'
517
+ @results[1][:title].should eq 'gorilla'
518
+ end
519
+ end
520
+
521
+ describe '#minus' do
522
+ it 'subtracts stuff' do
523
+ @results = xpath { |x| x.descendant(:p)[XPath.position().minus(1) == 0] }
524
+ @results[0][:id].should eq 'fooDiv'
525
+ @results[1][:title].should eq 'gorilla'
346
526
  end
347
527
  end
348
528
 
529
+ describe '#multiply' do
530
+ it 'multiplies stuff' do
531
+ @results = xpath { |x| x.descendant(:p)[XPath.position() * 3 == 3] }
532
+ @results[0][:id].should eq 'fooDiv'
533
+ @results[1][:title].should eq 'gorilla'
534
+ end
535
+ end
536
+
537
+ describe '#divide' do
538
+ it 'divides stuff' do
539
+ @results = xpath { |x| x.descendant(:p)[XPath.position() / 2 == 1] }
540
+ @results[0].text.should eq 'Bax'
541
+ @results[1].text.should eq 'Bax'
542
+ end
543
+ end
544
+
545
+ describe '#mod' do
546
+ it 'take modulo' do
547
+ @results = xpath { |x| x.descendant(:p)[XPath.position() % 2 == 1] }
548
+ @results[0].text.should eq 'Blah'
549
+ @results[1][:title].should eq 'monkey'
550
+ @results[2][:title].should eq 'gorilla'
551
+ end
552
+ end
553
+
554
+ describe '#ancestor' do
555
+ it 'finds ancestor nodes' do
556
+ @results = xpath { |x| x.descendant(:p)[1].ancestor }
557
+ @results[0].node_name.should eq 'html'
558
+ @results[1].node_name.should eq 'body'
559
+ @results[2][:id].should eq 'foo'
560
+ end
561
+ end
349
562
  end