rspec_hpricot_matchers 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.
@@ -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,71 @@
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
+ == Usage
8
+
9
+ As its first argument, have_tag() accepts any CSS or XPath selectors
10
+ which are supported by Hpricot.
11
+
12
+ body.should have_tag('form[@action*=session]')
13
+ body.should have_tag('ul > li + li')
14
+
15
+ Expectations can be placed upon the inner text of the matched element
16
+ by providing another argument, which should be either a String or a
17
+ Regexp:
18
+
19
+ body.should have_tag('h1', 'Welcome')
20
+ body.should have_tag('p', /a very important blurb/i)
21
+
22
+ Expectations can be placed upon the number of matched elements by
23
+ passing an options hash:
24
+
25
+ body.should have_tag('abbr', :count => 1) # exactly one
26
+ body.should have_tag('dt', :minimum => 4) # at least 4
27
+ body.should have_tag('dd', :maximum => 4) # at most 4
28
+ body.should have_tag('a.outgoing', /rspec/i, :count => 2)
29
+
30
+ The :count key also accepts a Range, making the following equivalent:
31
+
32
+ body.should have_tag('tr', :count => 3..5)
33
+ body.should have_tag('tr', :minimum => 3,
34
+ :maximum => 5)
35
+
36
+
37
+ The usage of with_tag(), however, is no longer supported. Instead, a
38
+ block passed to have_tag() will have each matched element successively
39
+ yielded to it. If none of the blocks return without raising an
40
+ ExpectationNotMetError, the outer have_tag() is treated as having failed:
41
+
42
+ body.should have_tag('thead') do |thead|
43
+ thead.should have_tag('th', :count => 5)
44
+ end
45
+
46
+ This also allows arbitrary expectations to be applied from within
47
+ the block, such as:
48
+
49
+ body.should have_tag('dl dd.sha1') do |dd|
50
+ dd.inner_text.length.should == 40
51
+ end
52
+
53
+
54
+ == Notes
55
+
56
+ Currently, this implementation does not support substitution values
57
+ as assert_select did (by way of HTML::Selector):
58
+
59
+ # Not yet supported:
60
+ body.should have_tag('li[class=?]', dom_class)
61
+ body.should have_tag('tr.person#?', /^person-\d+$/)
62
+
63
+ I personally rarely use these, and Hpricot's advanced selectors make
64
+ them mostly useless, as far as I can tell, so I am unlikely to
65
+ implement them myself.
66
+
67
+ This have_tag() further differs from the assert_select-based
68
+ implementation in that the nested have_tag() calls must *all* pass
69
+ on a single selected element in order to be true. This was a source
70
+ of confusion in RSpec ticket #316. There is a spec covering this
71
+ case if you need an example.
@@ -0,0 +1,27 @@
1
+ require 'rake/gempackagetask'
2
+ require 'spec/rake/spectask'
3
+
4
+ task :default => :spec
5
+ Spec::Rake::SpecTask.new
6
+
7
+ gemspec = Gem::Specification.new do |spec|
8
+ spec.name = 'rspec_hpricot_matchers'
9
+ spec.summary = "Implementation of have_tag() rspec matcher using Hpricot"
10
+ spec.version = '1.0'
11
+ spec.author = 'Kyle Hargraves'
12
+ spec.email = 'philodespotos@gmail.com'
13
+ spec.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
+ spec.files = FileList['lib/**/*', 'spec/**/*', 'README', 'MIT-LICENSE', 'Rakefile']
21
+
22
+ spec.rubyforge_project = 'rspec-hpricot'
23
+ spec.homepage = 'http://rspec-hpricot.rubyforge.org'
24
+ end
25
+
26
+ Rake::GemPackageTask.new(gemspec) do |spec|
27
+ end
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'hpricot'
3
+ require 'rspec_hpricot_matchers/have_tag'
4
+
5
+ # evil hack to duck-type CgiResponse so that nested shoulds can use
6
+ # +rspec_on_rails+ matchers without remembering to call to_s on it
7
+ #
8
+ # e.g.
9
+ #
10
+ # response.should have_tag("li") do |ul|
11
+ # ul.should have_text("List Item") # with hack
12
+ # ul.to_s.should have_text("List Item") # without hack
13
+ # end
14
+ class Hpricot::Elem
15
+ alias body to_s
16
+ 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 Hpricot === input
50
+ input
51
+ elsif input.respond_to?(:body)
52
+ Hpricot(input.body)
53
+ else
54
+ Hpricot(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,244 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ unless defined?(SpecFailed)
4
+ SpecFailed = Spec::Expectations::ExpectationNotMetError
5
+ end
6
+
7
+ describe 'have_tag' do
8
+ before(:each) do
9
+ @html = "<ul><li>An egregiously long string</li></ul>"
10
+ end
11
+
12
+ it "should match against strings" do
13
+ @html.should have_tag('li')
14
+ end
15
+
16
+ it "should match against Hpricot documents" do
17
+ hdoc = Hpricot(@html)
18
+ hdoc.should have_tag('li')
19
+ end
20
+
21
+ it "should use the response of #body if the target responds to it" do
22
+ response = Object.new
23
+ class << response
24
+ def body
25
+ "<ul><li>An egregiously long string</li></ul>"
26
+ end
27
+ end
28
+ response.should have_tag('li')
29
+ end
30
+
31
+ it "should not match when the target does not have the selected element" do
32
+ @html.should_not have_tag('dd')
33
+ end
34
+
35
+ it "should match against the inner text of the selected element" do
36
+ @html.should have_tag('li', 'An egregiously long string')
37
+ end
38
+
39
+ it "should match negatively against the inner text" do
40
+ @html.should_not have_tag('li', 'Some other string entirely')
41
+ end
42
+
43
+ it "should match against a Regexp describing the inner text" do
44
+ @html.should have_tag('li', /GREG/i)
45
+ end
46
+
47
+ it "should match negatively against a Regexp describing the inner text" do
48
+ @html.should_not have_tag('li', /GREG/)
49
+ end
50
+
51
+ it "should include the body in the failure message" do
52
+ lambda {
53
+ @html.should have_tag('abbr')
54
+ }.should raise_error(SpecFailed, /#{Regexp.escape(@html)}/)
55
+ end
56
+
57
+ it "should include the selector in the failure message" do
58
+ lambda {
59
+ @html.should have_tag('abbr')
60
+ }.should raise_error(SpecFailed, /"abbr"/)
61
+ end
62
+
63
+ it "should include the expected inner text if provided" do
64
+ lambda {
65
+ @html.should have_tag('li', /something else/)
66
+ }.should raise_error(SpecFailed, %r{/something else/})
67
+ end
68
+ end
69
+
70
+ describe 'have_tag inner expectations' do
71
+ before(:each) do
72
+ @html = "<ul><li>An egregiously long string</li></ul>"
73
+ end
74
+
75
+ it "should fail when the outer selector fails" do
76
+ lambda {
77
+ @html.should have_tag('dl') do |dl|
78
+ dl.should have_tag('li')
79
+ end
80
+ }.should raise_error(SpecFailed)
81
+ end
82
+
83
+ it "should match the inner expectations against the elements matched by the outer selector" do
84
+ @html.should have_tag('ul') do |ul|
85
+ ul.should have_tag('li')
86
+ end
87
+ end
88
+
89
+ it "should support negated inner expectations" do
90
+ @html.should have_tag('ul') do |ul|
91
+ ul.should_not have_tag('dd')
92
+ end
93
+ end
94
+
95
+ it "should treat multiple nested have_tag() expectations as a logical AND" do
96
+ @html.should have_tag('ul') do |ul|
97
+ ul.should have_tag('li')
98
+ ul.should_not have_tag('dd')
99
+ end
100
+ end
101
+
102
+ it "should only match against a single element at a time when nesting expectations (see RSpec LH#316)" do
103
+ html = <<-EOHTML
104
+ <ul>
105
+ <li>
106
+ <a href="1">1</a>
107
+ </li>
108
+ <li>
109
+ <a href="2">2</a>
110
+ <span>Hello</span>
111
+ </li>
112
+ </ul>
113
+ EOHTML
114
+
115
+ html.should have_tag('li', :count => 1) do |li|
116
+ li.should have_tag('a')
117
+ li.should have_tag('span')
118
+ end
119
+
120
+ html.should have_tag('li', :count => 1) do |li|
121
+ li.should have_tag('a')
122
+ li.should_not have_tag('span')
123
+ end
124
+
125
+ html.should have_tag('li', :count => 2) do |li|
126
+ li.should have_tag('a')
127
+ end
128
+ end
129
+
130
+ it "should yield elements which respond to #body" do
131
+ @html.should have_tag('ul') do |ul|
132
+ ul.should respond_to(:body)
133
+ end
134
+ end
135
+
136
+ it "should supports arbitrary expectations within the block" do
137
+ html = %q{<span class="sha1">cbc0bd52f99fe19304bccad383694e92b8ee2c71</span>}
138
+ html.should have_tag('span.sha1') do |span|
139
+ span.inner_text.length.should == 40
140
+ end
141
+ html.should_not have_tag('span.sha1') do |span|
142
+ span.inner_text.length.should == 41
143
+ end
144
+ end
145
+
146
+ it "should include a description of the inner expectation in the failure message" do
147
+ pending "No idea how to implement this."
148
+ end
149
+ end
150
+
151
+ describe 'have_tag with counts' do
152
+ before(:each) do
153
+ @html = <<-EOHTML
154
+ <ul>
155
+ <li>Foo</li>
156
+ <li>Bar</li>
157
+ <li>Foo again</li>
158
+ <li><a href="/baz">With inner elements</a></li>
159
+ </ul>
160
+ EOHTML
161
+ end
162
+
163
+ it "should treat an integer :count as expecting exactly n matched elements" do
164
+ @html.should have_tag('li', :count => 4)
165
+ @html.should_not have_tag('li', :count => 3)
166
+ @html.should_not have_tag('li', :count => 5)
167
+ end
168
+
169
+ it "should treat a range :count as expecting between x and y matched elements" do
170
+ @html.should have_tag('li', :count => 1..5)
171
+ @html.should_not have_tag('li', :count => 2..3)
172
+ end
173
+
174
+ it "should treat a :count of zero as if a negative match were expected" do
175
+ @html.should have_tag('dd', :count => 0)
176
+ end
177
+
178
+ it "should treat :minimum as expecting at least n matched elements" do
179
+ (0..4).each { |n| @html.should have_tag('li', :minimum => n) }
180
+ @html.should_not have_tag('li', :minimum => 5)
181
+ end
182
+
183
+ it "should treat :maximum as expecting at most n matched elements" do
184
+ @html.should_not have_tag('li', :maximum => 3)
185
+ @html.should have_tag('li', :maximum => 4)
186
+ @html.should have_tag('li', :maximum => 5)
187
+ end
188
+
189
+ it "should support matching of content while specifying a count" do
190
+ @html.should have_tag('li', /foo/i, :count => 2)
191
+ end
192
+
193
+ it "should work when the have_tag is nested" do
194
+ @html.should have_tag('ul') do |ul|
195
+ ul.should have_tag('li', :minimum => 2)
196
+ ul.should have_tag('li', /foo/i, :count => 2)
197
+ end
198
+ end
199
+
200
+ it "should include the actual number of elements matched in the failure message" do
201
+ lambda {
202
+ @html.should have_tag('li', :count => 3)
203
+ }.should raise_error(SpecFailed, /found 4/)
204
+ lambda {
205
+ @html.should have_tag('li', :count => 5)
206
+ }.should raise_error(SpecFailed, /found 4/)
207
+ end
208
+
209
+ it "should include the actual number of elements matched in the negative failure message" do
210
+ lambda {
211
+ @html.should_not have_tag('li', :count => 4)
212
+ }.should raise_error(SpecFailed, /found 4/)
213
+ end
214
+
215
+ it "should include the expected number of elements in the failure message" do
216
+ lambda {
217
+ @html.should have_tag('li', :count => 2)
218
+ }.should raise_error(SpecFailed, /to have 2 elements matching/)
219
+ end
220
+
221
+ it "should include the expected number of elements in the negative failure message" do
222
+ lambda {
223
+ @html.should_not have_tag('li', :count => 4)
224
+ }.should raise_error(SpecFailed, /to have 4 elements matching/)
225
+ end
226
+
227
+ it "should describe the :minimum case using 'at least ...'" do
228
+ lambda {
229
+ @html.should have_tag('li', :minimum => 80)
230
+ }.should raise_error(SpecFailed, /to have at least 80 elements matching/)
231
+ end
232
+
233
+ it "should describe the :maximum case using 'at most ...'" do
234
+ lambda {
235
+ @html.should have_tag('li', :maximum => 2)
236
+ }.should raise_error(SpecFailed, /to have at most 2 elements matching/)
237
+ end
238
+
239
+ it "should describe the :minimum and :maximum case using 'at least ... and at most ...'" do
240
+ lambda {
241
+ @html.should have_tag('li', :minimum => 8, :maximum => 30)
242
+ }.should raise_error(SpecFailed, /to have at least 8 and at most 30 elements matching/)
243
+ end
244
+ end
@@ -0,0 +1,6 @@
1
+ $: << File.dirname(__FILE__) + '/../lib'
2
+ require 'rspec_hpricot_matchers'
3
+
4
+ Spec::Runner.configure do |config|
5
+ config.include(RspecHpricotMatchers)
6
+ end
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rspec_hpricot_matchers
3
+ version: !ruby/object:Gem::Version
4
+ version: "1.0"
5
+ platform: ruby
6
+ authors:
7
+ - Kyle Hargraves
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-03-26 00:00:00 -05: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: philodespotos@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - lib/rspec_hpricot_matchers
26
+ - lib/rspec_hpricot_matchers/have_tag.rb
27
+ - lib/rspec_hpricot_matchers.rb
28
+ - spec/rspec_hpricot_matchers
29
+ - spec/rspec_hpricot_matchers/have_tag_spec.rb
30
+ - spec/spec_helper.rb
31
+ - README
32
+ - MIT-LICENSE
33
+ - Rakefile
34
+ has_rdoc: false
35
+ homepage: http://rspec-hpricot.rubyforge.org
36
+ post_install_message:
37
+ rdoc_options: []
38
+
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: rspec-hpricot
56
+ rubygems_version: 1.0.1
57
+ signing_key:
58
+ specification_version: 2
59
+ summary: Implementation of have_tag() rspec matcher using Hpricot
60
+ test_files: []
61
+