wgibbs-xpath 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,27 @@
1
+ module XPath
2
+ class Union
3
+ include Enumerable
4
+
5
+ attr_reader :expressions
6
+
7
+ def initialize(*expressions)
8
+ @expressions = expressions
9
+ end
10
+
11
+ def each(&block)
12
+ expressions.each(&block)
13
+ end
14
+
15
+ def to_xpath(predicate=nil)
16
+ expressions.map { |e| e.to_xpath(predicate) }.join(' | ')
17
+ end
18
+
19
+ def to_xpaths
20
+ [to_xpath(:exact), to_xpath(:fuzzy)].uniq
21
+ end
22
+
23
+ def method_missing(*args)
24
+ XPath::Union.new(*expressions.map { |e| e.send(*args) })
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,3 @@
1
+ module XPath
2
+ VERSION = '0.1.4'
3
+ end
@@ -0,0 +1,87 @@
1
+ <h1>Form</h1>
2
+
3
+ <p>
4
+ <a id="awesome-link" data="link-text" href="#link">An awesome link</a>
5
+ <a id="some-id" data="link-id" href="#id">With id</a>
6
+ <a title="My title" data="link-title" href="#title">A cool title</a>
7
+ <a href="#image" data="link-img"><img src="foo.png" alt="Alt link"/></a>
8
+ <a title="This title is too long" data="link-fuzzy" href="#bar">A link at a time</a>
9
+ <a title="This title" data="link-exact" href="#foo">A link</a>
10
+ <a href="#bar" data="link-img-fuzzy"><img src="foo.png" alt="An image that is beautiful"/></a>
11
+ <a href="#foo" data="link-img-exact"><img src="foo.ong" alt="An image"/></a>
12
+ <a href="http://www.example.com" data="link-href">Href-ed link</a>
13
+ <a>Wrong Link</a>
14
+ <a href="#spacey" data="link-whitespace"> My
15
+
16
+ whitespaced
17
+ link</a>
18
+ <a href="#has-children" data="link-children">
19
+ An <em>emphatic</em> link with some children
20
+ </a>
21
+ </p>
22
+
23
+ <p>
24
+ <input type="submit" id="submit-with-id" data="id-submit" value="Has ID"/>
25
+ <input type="submit" value="submit-with-value" data="value-submit"/>
26
+ <input type="submit" value="not exact value submit" data="not-exact-value-submit"/>
27
+ <input type="submit" value="exact value submit" data="exact-value-submit"/>
28
+
29
+ <input type="button" id="button-with-id" data="id-button" value="Has ID"/>
30
+ <input type="button" value="button-with-value" data="value-button"/>
31
+ <input type="button" value="not exact value button" data="not-exact-value-button"/>
32
+ <input type="button" value="exact value button" data="exact-value-button"/>
33
+
34
+ <input type="image" id="imgbut-with-id" data="id-imgbut" value="Has ID"/>
35
+ <input type="image" value="imgbut-with-value" data="value-imgbut"/>
36
+ <input type="image" alt="imgbut-with-alt" data="alt-imgbut"/>
37
+ <input type="image" value="not exact value imgbut" data="not-exact-value-imgbut"/>
38
+ <input type="image" value="exact value imgbut" data="exact-value-imgbut"/>
39
+
40
+ <button id="btag-with-id" data="id-btag" value="Has ID"/>
41
+ <button value="btag-with-value" data="value-btag"/>
42
+ <button value="not exact value btag" data="not-exact-value-btag"/>
43
+ <button value="exact value btag" data="exact-value-btag"/>
44
+
45
+ <button data="text-btag">btag-with-text</button>
46
+ <button data="not-exact-text-btag">not exact text btag</button>
47
+ <button data="exact-text-btag">exact text btag</button>
48
+
49
+ <button data="btag-with-whitespace"> My
50
+
51
+ whitespaced
52
+ button</button>
53
+ <button data="btag-with-children">
54
+ An <em>emphatic</em> button with some children
55
+ </button>
56
+
57
+ <input type="schmoo" value="schmoo button" data="schmoo"/>
58
+ <label for="text-with-id">Label text <input type="text" id="text-with-id" data="id-text" value="monkey"/></label>
59
+ <input type="text" id="text-with-placeholder" data="placeholder-text" value="monkey" placeholder="Placeholder text"/>
60
+ <label for="problem-text-with-id">Label text's got an apostrophe <input type="text" id="problem-text-with-id" data="id-problem-text" value="monkey"/></label>
61
+ </p>
62
+
63
+ <p>
64
+ <fieldset id="some-fieldset-id" data="fieldset-id"></fieldset>
65
+ <fieldset data="fieldset-legend"><legend>Some Legend</legend></fieldset>
66
+ <fieldset data="fieldset-legend-span"><legend><span>Span Legend</span></legend></fieldset>
67
+ <fieldset data="fieldset-fuzzy"><legend>Long legend yo</legend></fieldset>
68
+ <fieldset data="fieldset-exact"><legend>Long legend</legend></fieldset>
69
+ </p>
70
+
71
+ <p>
72
+ <select>
73
+ <optgroup label="Group A" data="optgroup-a"></optgroup>
74
+ <optgroup label="Group B" data="optgroup-b"></optgroup>
75
+ </select>
76
+ </p>
77
+
78
+ <p>
79
+ <table id="whitespaced-table" data="table-with-whitespace">
80
+ <tr>
81
+ <td data="cell-whitespaced">I have
82
+ <span>nested whitespace</span>
83
+ </td>
84
+ <td>I don't</td>
85
+ </tr>
86
+ </table>
87
+ </p>
@@ -0,0 +1,32 @@
1
+ <div id="bar" title="barDiv">
2
+
3
+ </div>
4
+
5
+ <div title="noId"></div>
6
+
7
+ <div id="foo" title="fooDiv" data="id">
8
+ <p id="fooDiv">Blah</p>
9
+ <p>Bax</p>
10
+ <p title="monkey">Bax</p>
11
+ <ul><li>A list</li></ul>
12
+ </div>
13
+
14
+ <div id="baz" title="bazDiv"></div>
15
+
16
+ <div id="preference">
17
+ <p id="is-fuzzy">allamas</p>
18
+ <p id="is-exact">llama</p>
19
+ </div>
20
+
21
+ <p id="whitespace">
22
+ A lot
23
+
24
+ of
25
+ whitespace
26
+ </p>
27
+
28
+ <div id="moar">
29
+ <p id="impchay">chimp</p>
30
+ <div id="elephantay">elephant</div>
31
+ <p id="amingoflay">flamingo</p>
32
+ </div>
@@ -0,0 +1,43 @@
1
+ <h1>This is a test</h1>
2
+
3
+ <p id="first">
4
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
5
+ tempor incididunt ut <a href="/with_simple_html" title="awesome title" class="simple">labore</a>
6
+ et dolore magna aliqua. Ut enim ad minim veniam,
7
+ quis nostrud exercitation <a href="/foo" id="foo">ullamco</a> laboris nisi
8
+ ut aliquip ex ea commodo consequat.
9
+ <a href="/with_simple_html"><img src="http://www.foobar.sun/dummy_image.jpg" width="20" height="20" alt="awesome image" /></a>
10
+ </p>
11
+
12
+ <p id="second">
13
+ Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
14
+ dolore eu fugiat <a href="/redirect" id="red">Redirect</a> pariatur. Excepteur sint occaecat cupidatat non proident,
15
+ sunt in culpa qui officia
16
+ text with
17
+ whitespace
18
+ id est laborum.
19
+ </p>
20
+
21
+ <p>
22
+ <input type="text" id="test_field" value="monkey"/>
23
+ <textarea>banana</textarea>
24
+ <a href="/redirect_back">BackToMyself</a>
25
+ <a title="twas a fine link" href="/redirect">A link came first</a>
26
+ <a title="a fine link" href="/with_simple_html">A link</a>
27
+ <a title="a fine link with data method" data-method="delete" href="/delete">A link with data-method</a>
28
+ <a>No Href</a>
29
+ <a href="">Blank Href</a>
30
+ <a href="#">Blank Anchor</a>
31
+ <a href="#anchor">Anchor</a>
32
+ <a href="/with_simple_html#anchor">Anchor on different page</a>
33
+ <a href="/with_html#anchor">Anchor on same page</a>
34
+ <input type="text" value="" id="test_field">
35
+ <input type="text" checked="checked" id="checked_field">
36
+ <a href="/redirect"><img src="http://www.foobar.sun/dummy_image.jpg" width="20" height="20" alt="very fine image" /></a>
37
+ <a href="/with_simple_html"><img src="http://www.foobar.sun/dummy_image.jpg" width="20" height="20" alt="fine image" /></a>
38
+ </p>
39
+
40
+ <div id="hidden" style="display: none;">
41
+ <div id="hidden_via_ancestor">Inside element with hidden ancestor</div>
42
+ <a href="/with_simple_html" title="awesome title" class="simple">hidden link</a>
43
+ </div>
@@ -0,0 +1,187 @@
1
+ require 'spec_helper'
2
+ require 'nokogiri'
3
+
4
+ describe XPath::HTML do
5
+ let(:template) { 'form' }
6
+ let(:template_path) { File.read(File.expand_path("fixtures/#{template}.html", File.dirname(__FILE__))) }
7
+ let(:doc) { Nokogiri::HTML(template_path) }
8
+
9
+ def get(*args)
10
+ all(*args).first
11
+ end
12
+
13
+ def all(*args)
14
+ XPath::HTML.send(subject, *args).to_xpaths.map do |xpath|
15
+ doc.xpath(xpath)
16
+ end.flatten.uniq.map { |node| node[:data] }
17
+ end
18
+
19
+ describe '#link' do
20
+ subject { :link }
21
+
22
+ it("finds links by id") { get('some-id').should == 'link-id' }
23
+ it("finds links by content") { get('An awesome link').should == 'link-text' }
24
+ it("finds links by content regardless of whitespace") { get('My whitespaced link').should == 'link-whitespace' }
25
+ it("finds links with child tags by content") { get('An emphatic link').should == 'link-children' }
26
+ it("finds links by the content of theur child tags") { get('emphatic').should == 'link-children' }
27
+ it("finds links by approximate content") { get('awesome').should == 'link-text' }
28
+ it("prefers exact matches of content") { all('A link').should == ['link-exact', 'link-fuzzy'] }
29
+ it("finds links by title") { get('My title').should == 'link-title' }
30
+ it("finds links by approximate title") { get('title').should == 'link-title' }
31
+ it("prefers exact matches of title") { all('This title').should == ['link-exact', 'link-fuzzy'] }
32
+ it("finds links by image's alt attribute") { get('Alt link').should == 'link-img' }
33
+ it("finds links by image's approximate alt attribute") { get('Alt').should == 'link-img' }
34
+ it("prefers exact matches of image's alt attribute") { all('An image').should == ['link-img-exact', 'link-img-fuzzy'] }
35
+ it("does not find links without href attriutes") { get('Wrong Link').should be_nil }
36
+ it("finds links with an href") { get("Href-ed link", :href => 'http://www.example.com').should == 'link-href' }
37
+ it("does not find links with an incorrect href") { get("Href-ed link", :href => 'http://www.somewhere.com').should be_nil }
38
+ end
39
+
40
+ describe '#button' do
41
+ subject { :button }
42
+
43
+ context "with submit type" do
44
+ it("finds buttons by id") { get('submit-with-id').should == 'id-submit' }
45
+ it("finds buttons by value") { get('submit-with-value').should == 'value-submit' }
46
+ it("finds buttons by approximate value") { get('mit-with-val').should == 'value-submit' }
47
+ it("prefers buttons with exact value") { all('exact value submit').should == ['exact-value-submit', 'not-exact-value-submit'] }
48
+ end
49
+
50
+ context "with button type" do
51
+ it("finds buttons by id") { get('button-with-id').should == 'id-button' }
52
+ it("finds buttons by value") { get('button-with-value').should == 'value-button' }
53
+ it("finds buttons by approximate value") { get('ton-with-val').should == 'value-button' }
54
+ it("prefers buttons with exact value") { all('exact value button').should == ['exact-value-button', 'not-exact-value-button'] }
55
+ end
56
+
57
+ context "with image type" do
58
+ it("finds buttons by id") { get('imgbut-with-id').should == 'id-imgbut' }
59
+ it("finds buttons by value") { get('imgbut-with-value').should == 'value-imgbut' }
60
+ it("finds buttons by approximate value") { get('gbut-with-val').should == 'value-imgbut' }
61
+ it("finds buttons by alt attribute") { get('imgbut-with-alt').should == 'alt-imgbut' }
62
+ it("prefers buttons with exact value") { all('exact value imgbut').should == ['exact-value-imgbut', 'not-exact-value-imgbut'] }
63
+ end
64
+
65
+ context "with button tag" do
66
+ it("finds buttons by id") { get('btag-with-id').should == 'id-btag' }
67
+ it("finds buttons by value") { get('btag-with-value').should == 'value-btag' }
68
+ it("finds buttons by approximate value") { get('tag-with-val').should == 'value-btag' }
69
+ it("finds prefers buttons with exact value") { all('exact value btag').should == ['exact-value-btag', 'not-exact-value-btag'] }
70
+ it("finds buttons by text") { get('btag-with-text').should == 'text-btag' }
71
+ it("finds buttons by text ignoring whitespace") { get('My whitespaced button').should == 'btag-with-whitespace' }
72
+ it("finds buttons by approximate text ") { get('tag-with-tex').should == 'text-btag' }
73
+ it("finds buttons with child tags by text") { get('An emphatic button').should == 'btag-with-children' }
74
+ it("finds buttons by text of their children") { get('emphatic').should == 'btag-with-children' }
75
+ it("prefers buttons with exact text") { all('exact text btag').should == ['exact-text-btag', 'not-exact-text-btag'] }
76
+ end
77
+
78
+ context "with unkown type" do
79
+ it("does not find the button") { get('schmoo button').should be_nil }
80
+ end
81
+ end
82
+
83
+ describe '#fieldset' do
84
+ subject { :fieldset }
85
+
86
+ it("finds fieldsets by id") { get('some-fieldset-id').should == 'fieldset-id' }
87
+ it("finds fieldsets by legend") { get('Some Legend').should == 'fieldset-legend' }
88
+ it("finds fieldsets by legend child tags") { get('Span Legend').should == 'fieldset-legend-span' }
89
+ it("accepts approximate legends") { get('Legend').should == 'fieldset-legend' }
90
+ it("prefers exact legend") { all('Long legend').should == ['fieldset-exact', 'fieldset-fuzzy'] }
91
+ end
92
+
93
+ describe '#field' do
94
+ subject { :field }
95
+
96
+ context "by id" do
97
+ it("finds inputs with text type") {}
98
+ it("finds inputs with password type") {}
99
+ it("finds inputs with custom type") {}
100
+ it("finds textareas") {}
101
+ it("finds select boxes") {}
102
+ it("does not find submit buttons") {}
103
+ it("does not find image buttons") {}
104
+ it("does not find hidden fields") {}
105
+ end
106
+
107
+ context "by name" do
108
+ it("finds inputs with text type") {}
109
+ it("finds inputs with password type") {}
110
+ it("finds inputs with custom type") {}
111
+ it("finds textareas") {}
112
+ it("finds select boxes") {}
113
+ it("does not find submit buttons") {}
114
+ it("does not find image buttons") {}
115
+ it("does not find hidden fields") {}
116
+ end
117
+
118
+ context "by referenced label" do
119
+ it("finds inputs with text type") {}
120
+ it("finds inputs with password type") {}
121
+ it("finds inputs with custom type") {}
122
+ it("finds textareas") {}
123
+ it("finds select boxes") {}
124
+ it("does not find submit buttons") {}
125
+ it("does not find image buttons") {}
126
+ it("does not find hidden fields") {}
127
+ end
128
+
129
+ context "by parent label" do
130
+ it("finds inputs with text type") { get('Label text').should == 'id-text' }
131
+ it("finds inputs where label has problem chars") { get("Label text's got an apostrophe").should == 'id-problem-text' }
132
+ it("finds inputs with password type") {}
133
+ it("finds inputs with custom type") {}
134
+ it("finds textareas") {}
135
+ it("finds select boxes") {}
136
+ it("does not find submit buttons") {}
137
+ it("does not find image buttons") {}
138
+ it("does not find hidden fields") {}
139
+ end
140
+
141
+ context "with :with option" do
142
+ it("finds inputs that match option") {}
143
+ it("omits inputs that don't match option") {}
144
+ it("finds textareas that match option") {}
145
+ it("omits textareas that don't match option") {}
146
+ end
147
+
148
+ context "with :checked option" do
149
+ context "when true" do
150
+ it("finds checked fields") {}
151
+ it("omits unchecked fields") {}
152
+ end
153
+ context "when false" do
154
+ it("finds unchecked fields") {}
155
+ it("omits checked fields") {}
156
+ end
157
+ context "when ommitted" do
158
+ it("finds unchecked fields") {}
159
+ it("finds checked fields") {}
160
+ end
161
+ end
162
+ end
163
+
164
+ describe '#fillable_field' do
165
+ subject{ :fillable_field }
166
+ context "by parent label" do
167
+ it("finds inputs with text type") { get('Label text').should == 'id-text' }
168
+ it("finds inputs where label has problem chars") { get("Label text's got an apostrophe").should == 'id-problem-text' }
169
+ end
170
+ context "by placeholder" do
171
+ it("finds inputs with placeholder text") { get('Placeholder text').should == 'placeholder-text' }
172
+ end
173
+
174
+ end
175
+
176
+ describe "#optgroup" do
177
+ subject { :optgroup }
178
+
179
+ it("finds optgroups by label") { get('Group A').should == 'optgroup-a' }
180
+ end
181
+
182
+ describe "#table" do
183
+ subject {:table}
184
+
185
+ it("finds cell content regardless of whitespace") {get('whitespaced-table', :rows => [["I have nested whitespace", "I don't"]]).should == 'table-with-whitespace'}
186
+ end
187
+ end
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'xpath'
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+
3
+ describe XPath::Union do
4
+ let(:template) { File.read(File.expand_path('fixtures/simple.html', File.dirname(__FILE__))) }
5
+ let(:doc) { Nokogiri::HTML(template) }
6
+
7
+ describe '#expressions' do
8
+ it "should return the expressions" do
9
+ @expr1 = XPath.generate { |x| x.descendant(:p) }
10
+ @expr2 = XPath.generate { |x| x.descendant(:div) }
11
+ @collection = XPath::Union.new(@expr1, @expr2)
12
+ @collection.expressions.should == [@expr1, @expr2]
13
+ end
14
+ end
15
+
16
+ describe '#each' do
17
+ it "should iterate through the expressions" do
18
+ @expr1 = XPath.generate { |x| x.descendant(:p) }
19
+ @expr2 = XPath.generate { |x| x.descendant(:div) }
20
+ @collection = XPath::Union.new(@expr1, @expr2)
21
+ exprs = []
22
+ @collection.each { |expr| exprs << expr }
23
+ exprs.should == [@expr1, @expr2]
24
+ end
25
+ end
26
+
27
+ describe '#map' do
28
+ it "should map the expressions" do
29
+ @expr1 = XPath.generate { |x| x.descendant(:p) }
30
+ @expr2 = XPath.generate { |x| x.descendant(:div) }
31
+ @collection = XPath::Union.new(@expr1, @expr2)
32
+ @collection.map { |expr| expr.class }.should == [XPath::Expression::Descendant, XPath::Expression::Descendant]
33
+ end
34
+ end
35
+
36
+ describe '#to_xpath' do
37
+ it "should create a valid xpath expression" do
38
+ @expr1 = XPath.generate { |x| x.descendant(:p) }
39
+ @expr2 = XPath.generate { |x| x.descendant(:div).where(x.attr(:id) == 'foo') }
40
+ @collection = XPath::Union.new(@expr1, @expr2)
41
+ @results = doc.xpath(@collection.to_xpath)
42
+ @results[0][:title].should == 'fooDiv'
43
+ @results[1].text.should == 'Blah'
44
+ @results[2].text.should == 'Bax'
45
+ end
46
+ end
47
+
48
+ describe '#where, #apply and others' do
49
+ it "should be delegated to the individual expressions" do
50
+ @expr1 = XPath.generate { |x| x.descendant(:p) }
51
+ @expr2 = XPath.generate { |x| x.descendant(:div) }
52
+ @collection = XPath::Union.new(@expr1, @expr2)
53
+ @xpath1 = @collection.where(XPath.attr(:id) == 'foo').to_xpath
54
+ @xpath2 = @collection.where(XPath.attr(:id) == XPath.varstring(:id)).apply(:id => 'fooDiv').to_xpath
55
+ @results = doc.xpath(@xpath1)
56
+ @results[0][:title].should == 'fooDiv'
57
+ @results = doc.xpath(@xpath2)
58
+ @results[0][:id].should == 'fooDiv'
59
+ end
60
+ end
61
+
62
+ end
63
+