wgibbs-xpath 0.1.4

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,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
+