pd-rspec_hpricot_matchers 1.1.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/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ pkg
2
+ *.iml
3
+ *.ipr
4
+ *.iws
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Kyle Hargraves
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOa AND
17
+ NONINFRINGEMENT. IN NO EVENT SaALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,88 @@
1
+ = rspec_hpricot_matchers
2
+
3
+ An implementation of have_tag(), as in rspec_on_rails, but sitting atop
4
+ Hpricot rather than merely wrapping assert_select().
5
+
6
+
7
+ == Installation
8
+
9
+ To use rspec_hpricot_matchers in your project, install the gem, and
10
+ add the following to your spec_helper.rb file:
11
+
12
+ require 'rspec_hpricot_matchers'
13
+ Spec::Runner.configure do |config|
14
+ config.include(RspecHpricotMatchers)
15
+ end
16
+
17
+ Similarly, to make the matchers available to stories, you can add the
18
+ following to your stories/helper.rb file:
19
+
20
+ require 'rspec_hpricot_matchers'
21
+ include RspecHpricotMatchers
22
+
23
+
24
+ == Usage
25
+
26
+ As its first argument, have_tag() accepts any CSS or XPath selectors
27
+ which are supported by Hpricot.
28
+
29
+ body.should have_tag('form[@action*=session]')
30
+ body.should have_tag('ul > li + li')
31
+
32
+ Expectations can be placed upon the inner text of the matched element
33
+ by providing another argument, which should be either a String or a
34
+ Regexp:
35
+
36
+ body.should have_tag('h1', 'Welcome')
37
+ body.should have_tag('p', /a very important blurb/i)
38
+
39
+ Expectations can be placed upon the number of matched elements by
40
+ passing an options hash:
41
+
42
+ body.should have_tag('abbr', :count => 1) # exactly one
43
+ body.should have_tag('dt', :minimum => 4) # at least 4
44
+ body.should have_tag('dd', :maximum => 4) # at most 4
45
+ body.should have_tag('a.outgoing', /rspec/i, :count => 2)
46
+
47
+ The :count key also accepts a Range, making the following equivalent:
48
+
49
+ body.should have_tag('tr', :count => 3..5)
50
+ body.should have_tag('tr', :minimum => 3,
51
+ :maximum => 5)
52
+
53
+
54
+ The usage of with_tag(), however, is no longer supported. Instead, a
55
+ block passed to have_tag() will have each matched element successively
56
+ yielded to it. If none of the blocks return without raising an
57
+ ExpectationNotMetError, the outer have_tag() is treated as having failed:
58
+
59
+ body.should have_tag('thead') do |thead|
60
+ thead.should have_tag('th', :count => 5)
61
+ end
62
+
63
+ This also allows arbitrary expectations to be applied from within
64
+ the block, such as:
65
+
66
+ body.should have_tag('dl dd.sha1') do |dd|
67
+ dd.inner_text.length.should == 40
68
+ end
69
+
70
+
71
+ == Notes
72
+
73
+ Currently, this implementation does not support substitution values
74
+ as assert_select did (by way of HTML::Selector):
75
+
76
+ # Not yet supported:
77
+ body.should have_tag('li[class=?]', dom_class)
78
+ body.should have_tag('tr.person#?', /^person-\d+$/)
79
+
80
+ I personally rarely use these, and Hpricot's advanced selectors make
81
+ them mostly useless, as far as I can tell, so I am unlikely to
82
+ implement them myself.
83
+
84
+ This have_tag() further differs from the assert_select-based
85
+ implementation in that the nested have_tag() calls must *all* pass
86
+ on a single selected element in order to be true. This was a source
87
+ of confusion in RSpec ticket #316. There is a spec covering this
88
+ case if you need an example.
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ require 'spec/rake/spectask'
2
+
3
+ task :default => :spec
4
+ Spec::Rake::SpecTask.new
5
+
6
+ begin
7
+ require 'jeweler'
8
+ Jeweler::Tasks.new do |gemspec|
9
+ gemspec.name = 'rspec_hpricot_matchers'
10
+ gemspec.author = 'Kyle Hargraves'
11
+ gemspec.email = 'pd@krh.me'
12
+ gemspec.summary = "Implementation of have_tag() rspec matcher using Hpricot"
13
+ gemspec.description = <<-END
14
+ rspec_hpricot_matchers provides an implementation of rspec_on_rails'
15
+ have_tag() matcher which does not depend on Rails' assert_select.
16
+ Using Hpricot instead, the matcher is available to non-Rails projects,
17
+ and enjoys the full flexibility of Hpricot's advanced CSS and XPath
18
+ selector support.
19
+ END
20
+
21
+ # Really, I don't care about rubyforge. Maybe I'll get this
22
+ # working some day.
23
+ # gemspec.rubyforge_project = 'rspec-hpricot'
24
+ end
25
+ rescue LoadError
26
+ puts "Jeweler unavailable, packaging tasks can not be run. Install it, thanks."
27
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.1.0
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'nokogiri'
3
+
4
+ require 'rspec_hpricot_matchers/have_tag'
5
+
6
+ class Nokogiri::XML::Element
7
+ alias body to_s
8
+ end
@@ -0,0 +1,112 @@
1
+ module RspecHpricotMatchers
2
+ class HaveTag
3
+ def initialize(selector, inner_text_or_options, options, &block)
4
+ @selector = selector
5
+ if Hash === inner_text_or_options
6
+ @inner_text = nil
7
+ @options = inner_text_or_options
8
+ else
9
+ @inner_text = inner_text_or_options
10
+ @options = options
11
+ end
12
+ end
13
+
14
+ def matches?(actual, &block)
15
+ @actual = actual
16
+ @hdoc = hdoc_for(@actual)
17
+
18
+ matched_elements = @hdoc.search(@selector)
19
+ if matched_elements.empty?
20
+ return @options[:count] == 0
21
+ end
22
+
23
+ if @inner_text
24
+ matched_elements = filter_on_inner_text(matched_elements)
25
+ end
26
+
27
+ if block
28
+ matched_elements = filter_on_nested_expectations(matched_elements, block)
29
+ end
30
+
31
+ @actual_count = matched_elements.length
32
+ return false if not acceptable_count?(@actual_count)
33
+
34
+ !matched_elements.empty?
35
+ end
36
+
37
+ def failure_message
38
+ explanation = @actual_count ? "but found #{@actual_count}" : "but did not"
39
+ "expected\n#{@hdoc.to_s}\nto have #{failure_count_phrase} #{failure_selector_phrase}, #{explanation}"
40
+ end
41
+
42
+ def negative_failure_message
43
+ explanation = @actual_count ? "but found #{@actual_count}" : "but did"
44
+ "expected\n#{@hdoc.to_s}\nnot to have #{failure_count_phrase} #{failure_selector_phrase}, #{explanation}"
45
+ end
46
+
47
+ private
48
+ def hdoc_for(input)
49
+ if Nokogiri::XML::Document === input
50
+ input
51
+ elsif input.respond_to?(:body)
52
+ Nokogiri.parse(input.body)
53
+ else
54
+ Nokogiri.parse(input.to_s)
55
+ end
56
+ end
57
+
58
+ def filter_on_inner_text(elements)
59
+ elements.select do |el|
60
+ next(el.inner_text =~ @inner_text) if @inner_text.is_a?(Regexp)
61
+ el.inner_text == @inner_text
62
+ end
63
+ end
64
+
65
+ def filter_on_nested_expectations(elements, block)
66
+ elements.select do |el|
67
+ begin
68
+ block.call(el)
69
+ rescue Spec::Expectations::ExpectationNotMetError
70
+ false
71
+ else
72
+ true
73
+ end
74
+ end
75
+ end
76
+
77
+ def acceptable_count?(actual_count)
78
+ if @options[:count]
79
+ return false unless @options[:count] === actual_count
80
+ end
81
+ if @options[:minimum]
82
+ return false unless actual_count >= @options[:minimum]
83
+ end
84
+ if @options[:maximum]
85
+ return false unless actual_count <= @options[:maximum]
86
+ end
87
+ true
88
+ end
89
+
90
+ def failure_count_phrase
91
+ if @options[:count]
92
+ "#{@options[:count]} elements matching"
93
+ elsif @options[:minimum] || @options[:maximum]
94
+ count_explanations = []
95
+ count_explanations << "at least #{@options[:minimum]}" if @options[:minimum]
96
+ count_explanations << "at most #{@options[:maximum]}" if @options[:maximum]
97
+ "#{count_explanations.join(' and ')} elements matching"
98
+ else
99
+ "an element matching"
100
+ end
101
+ end
102
+
103
+ def failure_selector_phrase
104
+ phrase = @selector.inspect
105
+ phrase << (@inner_text ? " with inner text #{@inner_text.inspect}" : "")
106
+ end
107
+ end
108
+
109
+ def have_tag(selector, inner_text_or_options = nil, options = {}, &block)
110
+ HaveTag.new(selector, inner_text_or_options, options, &block)
111
+ end
112
+ end
@@ -0,0 +1,240 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe 'have_tag' do
4
+ before(:each) do
5
+ @html = "<ul><li>An egregiously long string</li></ul>"
6
+ end
7
+
8
+ it "should match against strings" do
9
+ @html.should have_tag('li')
10
+ end
11
+
12
+ it "should match against nokogiri documents" do
13
+ hdoc = Nokogiri::HTML(@html)
14
+ hdoc.should have_tag('li')
15
+ end
16
+
17
+ it "should use the response of #body if the target responds to it" do
18
+ response = Object.new
19
+ class << response
20
+ def body
21
+ "<ul><li>An egregiously long string</li></ul>"
22
+ end
23
+ end
24
+ response.should have_tag('li')
25
+ end
26
+
27
+ it "should not match when the target does not have the selected element" do
28
+ @html.should_not have_tag('dd')
29
+ end
30
+
31
+ it "should match against the inner text of the selected element" do
32
+ @html.should have_tag('li', 'An egregiously long string')
33
+ end
34
+
35
+ it "should match negatively against the inner text" do
36
+ @html.should_not have_tag('li', 'Some other string entirely')
37
+ end
38
+
39
+ it "should match against a Regexp describing the inner text" do
40
+ @html.should have_tag('li', /GREG/i)
41
+ end
42
+
43
+ it "should match negatively against a Regexp describing the inner text" do
44
+ @html.should_not have_tag('li', /GREG/)
45
+ end
46
+
47
+ it "should include the body in the failure message" do
48
+ lambda {
49
+ @html.should have_tag('abbr')
50
+ }.should raise_error(SpecFailed, /#{Regexp.escape(@html)}/)
51
+ end
52
+
53
+ it "should include the selector in the failure message" do
54
+ lambda {
55
+ @html.should have_tag('abbr')
56
+ }.should raise_error(SpecFailed, /"abbr"/)
57
+ end
58
+
59
+ it "should include the expected inner text if provided" do
60
+ lambda {
61
+ @html.should have_tag('li', /something else/)
62
+ }.should raise_error(SpecFailed, %r{/something else/})
63
+ end
64
+ end
65
+
66
+ describe 'have_tag inner expectations' do
67
+ before(:each) do
68
+ @html = "<ul><li>An egregiously long string</li></ul>"
69
+ end
70
+
71
+ it "should fail when the outer selector fails" do
72
+ lambda {
73
+ @html.should have_tag('dl') do |dl|
74
+ dl.should have_tag('li')
75
+ end
76
+ }.should raise_error(SpecFailed)
77
+ end
78
+
79
+ it "should match the inner expectations against the elements matched by the outer selector" do
80
+ @html.should have_tag('ul') do |ul|
81
+ ul.should have_tag('li')
82
+ end
83
+ end
84
+
85
+ it "should support negated inner expectations" do
86
+ @html.should have_tag('ul') do |ul|
87
+ ul.should_not have_tag('dd')
88
+ end
89
+ end
90
+
91
+ it "should treat multiple nested have_tag() expectations as a logical AND" do
92
+ @html.should have_tag('ul') do |ul|
93
+ ul.should have_tag('li')
94
+ ul.should_not have_tag('dd')
95
+ end
96
+ end
97
+
98
+ it "should only match against a single element at a time when nesting expectations (see RSpec LH#316)" do
99
+ html = <<-EOHTML
100
+ <ul>
101
+ <li>
102
+ <a href="1">1</a>
103
+ </li>
104
+ <li>
105
+ <a href="2">2</a>
106
+ <span>Hello</span>
107
+ </li>
108
+ </ul>
109
+ EOHTML
110
+
111
+ html.should have_tag('li', :count => 1) do |li|
112
+ li.should have_tag('a')
113
+ li.should have_tag('span')
114
+ end
115
+
116
+ html.should have_tag('li', :count => 1) do |li|
117
+ li.should have_tag('a')
118
+ li.should_not have_tag('span')
119
+ end
120
+
121
+ html.should have_tag('li', :count => 2) do |li|
122
+ li.should have_tag('a')
123
+ end
124
+ end
125
+
126
+ it "should yield elements which respond to #body" do
127
+ @html.should have_tag('ul') do |ul|
128
+ ul.should respond_to(:body)
129
+ end
130
+ end
131
+
132
+ it "should supports arbitrary expectations within the block" do
133
+ html = %q{<span class="sha1">cbc0bd52f99fe19304bccad383694e92b8ee2c71</span>}
134
+ html.should have_tag('span.sha1') do |span|
135
+ span.inner_text.length.should == 40
136
+ end
137
+ html.should_not have_tag('span.sha1') do |span|
138
+ span.inner_text.length.should == 41
139
+ end
140
+ end
141
+
142
+ it "should include a description of the inner expectation in the failure message" do
143
+ pending "No idea how to implement this."
144
+ end
145
+ end
146
+
147
+ describe 'have_tag with counts' do
148
+ before(:each) do
149
+ @html = <<-EOHTML
150
+ <ul>
151
+ <li>Foo</li>
152
+ <li>Bar</li>
153
+ <li>Foo again</li>
154
+ <li><a href="/baz">With inner elements</a></li>
155
+ </ul>
156
+ EOHTML
157
+ end
158
+
159
+ it "should treat an integer :count as expecting exactly n matched elements" do
160
+ @html.should have_tag('li', :count => 4)
161
+ @html.should_not have_tag('li', :count => 3)
162
+ @html.should_not have_tag('li', :count => 5)
163
+ end
164
+
165
+ it "should treat a range :count as expecting between x and y matched elements" do
166
+ @html.should have_tag('li', :count => 1..5)
167
+ @html.should_not have_tag('li', :count => 2..3)
168
+ end
169
+
170
+ it "should treat a :count of zero as if a negative match were expected" do
171
+ @html.should have_tag('dd', :count => 0)
172
+ end
173
+
174
+ it "should treat :minimum as expecting at least n matched elements" do
175
+ (0..4).each { |n| @html.should have_tag('li', :minimum => n) }
176
+ @html.should_not have_tag('li', :minimum => 5)
177
+ end
178
+
179
+ it "should treat :maximum as expecting at most n matched elements" do
180
+ @html.should_not have_tag('li', :maximum => 3)
181
+ @html.should have_tag('li', :maximum => 4)
182
+ @html.should have_tag('li', :maximum => 5)
183
+ end
184
+
185
+ it "should support matching of content while specifying a count" do
186
+ @html.should have_tag('li', /foo/i, :count => 2)
187
+ end
188
+
189
+ it "should work when the have_tag is nested" do
190
+ @html.should have_tag('ul') do |ul|
191
+ ul.should have_tag('li', :minimum => 2)
192
+ ul.should have_tag('li', /foo/i, :count => 2)
193
+ end
194
+ end
195
+
196
+ it "should include the actual number of elements matched in the failure message" do
197
+ lambda {
198
+ @html.should have_tag('li', :count => 3)
199
+ }.should raise_error(SpecFailed, /found 4/)
200
+ lambda {
201
+ @html.should have_tag('li', :count => 5)
202
+ }.should raise_error(SpecFailed, /found 4/)
203
+ end
204
+
205
+ it "should include the actual number of elements matched in the negative failure message" do
206
+ lambda {
207
+ @html.should_not have_tag('li', :count => 4)
208
+ }.should raise_error(SpecFailed, /found 4/)
209
+ end
210
+
211
+ it "should include the expected number of elements in the failure message" do
212
+ lambda {
213
+ @html.should have_tag('li', :count => 2)
214
+ }.should raise_error(SpecFailed, /to have 2 elements matching/)
215
+ end
216
+
217
+ it "should include the expected number of elements in the negative failure message" do
218
+ lambda {
219
+ @html.should_not have_tag('li', :count => 4)
220
+ }.should raise_error(SpecFailed, /to have 4 elements matching/)
221
+ end
222
+
223
+ it "should describe the :minimum case using 'at least ...'" do
224
+ lambda {
225
+ @html.should have_tag('li', :minimum => 80)
226
+ }.should raise_error(SpecFailed, /to have at least 80 elements matching/)
227
+ end
228
+
229
+ it "should describe the :maximum case using 'at most ...'" do
230
+ lambda {
231
+ @html.should have_tag('li', :maximum => 2)
232
+ }.should raise_error(SpecFailed, /to have at most 2 elements matching/)
233
+ end
234
+
235
+ it "should describe the :minimum and :maximum case using 'at least ... and at most ...'" do
236
+ lambda {
237
+ @html.should have_tag('li', :minimum => 8, :maximum => 30)
238
+ }.should raise_error(SpecFailed, /to have at least 8 and at most 30 elements matching/)
239
+ end
240
+ end
@@ -0,0 +1,10 @@
1
+ $: << File.dirname(__FILE__) + '/../lib'
2
+ require 'rspec_hpricot_matchers'
3
+
4
+ Spec::Runner.configure do |config|
5
+ config.include(RspecHpricotMatchers)
6
+ end
7
+
8
+ unless defined?(SpecFailed)
9
+ SpecFailed = Spec::Expectations::ExpectationNotMetError
10
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pd-rspec_hpricot_matchers
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kyle Hargraves
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-09 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: rspec_hpricot_matchers provides an implementation of rspec_on_rails' have_tag() matcher which does not depend on Rails' assert_select. Using Hpricot instead, the matcher is available to non-Rails projects, and enjoys the full flexibility of Hpricot's advanced CSS and XPath selector support.
17
+ email: pd@krh.me
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ files:
25
+ - .gitignore
26
+ - MIT-LICENSE
27
+ - README
28
+ - Rakefile
29
+ - VERSION
30
+ - lib/rspec_hpricot_matchers.rb
31
+ - lib/rspec_hpricot_matchers/have_tag.rb
32
+ - spec/rspec_hpricot_matchers/have_tag_spec.rb
33
+ - spec/spec_helper.rb
34
+ has_rdoc: true
35
+ homepage:
36
+ post_install_message:
37
+ rdoc_options:
38
+ - --charset=UTF-8
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ version:
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ requirements: []
54
+
55
+ rubyforge_project:
56
+ rubygems_version: 1.2.0
57
+ signing_key:
58
+ specification_version: 2
59
+ summary: Implementation of have_tag() rspec matcher using Hpricot
60
+ test_files:
61
+ - spec/rspec_hpricot_matchers/have_tag_spec.rb
62
+ - spec/spec_helper.rb