rspec-tag_matchers 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -22,7 +22,7 @@ containing HTML (this includes Nokogiri classes). They assume Rails conventions.
22
22
  <tt>rspec-tag_matchers</tt> is tested against Ruby 1.8.7, 1.9.2, 1.9.3, REE,
23
23
  Rubinius (1.8 mode) and JRuby.
24
24
 
25
- {<img src="http://travis-ci.org/dcuddeback/rspec-tag_matchers.png?branch=master" />}[http://travis-ci.org/dcuddeback/rspec-tag_matchers]
25
+ {<img src="https://secure.travis-ci.org/dcuddeback/rspec-tag_matchers.png?branch=master" />}[http://travis-ci.org/dcuddeback/rspec-tag_matchers]
26
26
 
27
27
  The library is well tested, but the collection of matchers is incomplete. I'm adding more on an
28
28
  as-needed basis. Please feel free to fork this repository to add your own then send me a pull
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.1.1
@@ -0,0 +1,140 @@
1
+ require 'rspec/tag_matchers/multiple_input_matcher'
2
+
3
+ module RSpec::TagMatchers
4
+ # Matches inputs generated by Rails' +date_select+ helper.
5
+ #
6
+ # @modifier discard
7
+ # Specifies that one or more components of the date are discarded in the +date_select+ helper.
8
+ # For example, <tt>discard(:day)</tt> should match the output of a +date_select+ helper that was
9
+ # given the option <tt>:discard_day => true</tt>. Note that this changes the matching criteria
10
+ # to expect a hidden input instead of a +<select>+ element for the components specified.
11
+ #
12
+ # @example Matching a date select for an event's +start_date+
13
+ # it { should have_date_select.for(:event => :start_date) }
14
+ #
15
+ # @example Matching a date select for a credit card expiration
16
+ # it { should have_date_select.discard(:day).for(:credit_card => :expiration) }
17
+ #
18
+ # @see HasDateSelect#discard
19
+ #
20
+ # @return [HasDateSelect]
21
+ def have_date_select
22
+ HasDateSelect.new
23
+ end
24
+
25
+ # A matcher that matches Rails' +date_select+ drop-downs.
26
+ class HasDateSelect < MultipleInputMatcher
27
+ include Helpers::SentenceHelper
28
+
29
+ # Initializes a HasDateSelect matcher.
30
+ def initialize
31
+ super('1i' => HasSelect.new, '2i' => HasSelect.new, '3i' => HasSelect.new)
32
+ end
33
+
34
+ # Specifies that one or more components of the date will be hidden by Rails' +date_select+
35
+ # helper. For example, calling +date_select+ with the option <tt>:discard_day => true</tt>
36
+ # produces two +<select>+ elments (for the year and month components) and one hidden input for
37
+ # the date component:
38
+ #
39
+ # <select name="model[date(1i)]">...</select>
40
+ # <select name="model[date(2i)]">...</select>
41
+ # <input type="hidden" name="model[date(3i)]" />
42
+ #
43
+ # Since the default is to match three +<select>+ elements, one must call +discard(:day)+ to tell
44
+ # a HasDateSelect matcher to expect a hidden input for the third component.
45
+ #
46
+ # @note This modifier replaces the matchers for the given date components. Any modifiers that
47
+ # were called before this one will be lost for those matchers. Therefor, +discard+ should be
48
+ # called before other modifiers, such as +for+. For example, the following matcher
49
+ # will only test the input names for the month and day components.
50
+ #
51
+ # it { should have_date_select.for(:start_date).discard(:year) }
52
+ #
53
+ # In order to test the input names for all three components, the +for+ modifier must come
54
+ # after the +discard+ modifier:
55
+ #
56
+ # it { should have_date_select.discard(:year).for(:start_date) }
57
+ #
58
+ # @param [Symbols] *parts A variable number of symbols specifying which components should be
59
+ # discarded. Valid symbols are +:year+, +:month+, and +:day+.
60
+ #
61
+ # @return [HasDateSelect] self
62
+ def discard(*parts)
63
+ @discard ||= []
64
+ @discard += parts
65
+
66
+ parts.each do |part|
67
+ # TODO: create a HasHiddenInput matcher
68
+ replace_matcher(part, HasInput.new.with_attribute(:type => :hidden))
69
+ end
70
+ self
71
+ end
72
+
73
+ # Returns a description of the matcher's criteria.
74
+ #
75
+ # @return [String]
76
+ def description
77
+ [basic_description, extra_description].compact.reject(&:empty?).join(" ")
78
+ end
79
+
80
+ # Returns an explanation of why the matcher failed to match with +should+.
81
+ #
82
+ # @return [String]
83
+ def failure_message
84
+ "expected document to #{description}; got: #{@rendered}"
85
+ end
86
+
87
+ # Returns an explanation of why the matcher failed to match with +should_not+.
88
+ #
89
+ # @return [String]
90
+ def negative_failure_message
91
+ "expected document to not #{description}; got: #{@rendered}"
92
+ end
93
+
94
+ private
95
+
96
+ # Replaces the matcher for the given date component.
97
+ #
98
+ # @param [Symbol] part The date component whose matcher should be replaced.
99
+ # @param [HasInput] new_matcher The matcher to replace the old one.
100
+ def replace_matcher(part, new_matcher)
101
+ key = key_for(part)
102
+ @components[key] = new_matcher.with_attribute(:name => /\(#{key}\)/)
103
+ end
104
+
105
+ # Looks up the key for the given date component.
106
+ #
107
+ # @param [Symbol] part A date component (+:year+, +:month+, or +:day+).
108
+ #
109
+ # @return [String] The requested key
110
+ def key_for(part)
111
+ {
112
+ :year => '1i',
113
+ :month => '2i',
114
+ :day => '3i'
115
+ }[part]
116
+ end
117
+
118
+ # Returns a basic description.
119
+ #
120
+ # @return [String]
121
+ def basic_description
122
+ "have date select"
123
+ end
124
+
125
+ # Provides an extra description fragment that can be appended to the basic description.
126
+ #
127
+ # @return [String]
128
+ def extra_description
129
+ [for_description, discard_description].compact.join(" ")
130
+ end
131
+
132
+ def for_description
133
+ "for #{@for.join(".")}" if @for
134
+ end
135
+
136
+ def discard_description
137
+ "without #{make_sentence(@discard, :conjunction => "or")}" if @discard
138
+ end
139
+ end
140
+ end
@@ -6,10 +6,16 @@ module RSpec::TagMatchers
6
6
  # @modifier for
7
7
  # Adds a criteria that the input must be for the given attribute.
8
8
  #
9
+ # @modifier value
10
+ # Adds a criteria that the input must have a given value.
11
+ #
9
12
  # @example Matching an input for the +name+ attribute on +user+
10
13
  # it { should have_input.for(:user => :name) }
11
14
  # it { should have_input.for(:user, :name) }
12
15
  #
16
+ # @example Matching an input with a value of <tt>"42"</tt>.
17
+ # it { should have_input.value("42") }
18
+ #
13
19
  # @return [HasInput]
14
20
  #
15
21
  # @see HasInput#for
@@ -88,6 +94,15 @@ module RSpec::TagMatchers
88
94
  self
89
95
  end
90
96
 
97
+ # Adds a criteria that the input's value must match a certain value.
98
+ #
99
+ # @param [String, Symbol, Regexp, true, false] value
100
+ #
101
+ # @return [HasInput] self
102
+ def value(value)
103
+ with_attribute(:value => value)
104
+ end
105
+
91
106
  private
92
107
 
93
108
  # Converts an array or hash of names to a name for an input form.
@@ -90,6 +90,7 @@ module RSpec::TagMatchers
90
90
  # provide additional methods that can be chained from the matcher to provide tag-specific
91
91
  # criteria. See {HasTag#with_criteria} for how to add custom criteria to a matcher.
92
92
  class HasTag
93
+ include Helpers::SentenceHelper
93
94
 
94
95
  # Constructs a matcher that matches HTML tags by +name+.
95
96
  #
@@ -100,6 +101,27 @@ module RSpec::TagMatchers
100
101
  @criteria = []
101
102
  end
102
103
 
104
+ # Returns a description of the matcher's criteria. The description is used in RSpec's output.
105
+ #
106
+ # @return [String]
107
+ def description
108
+ "have #{@name.inspect} tag #{extra_description}".strip
109
+ end
110
+
111
+ # Returns an explanation of why the matcher failed to match with +should+.
112
+ #
113
+ # @return [String]
114
+ def failure_message
115
+ "expected document to #{description}; got: #{@rendered}"
116
+ end
117
+
118
+ # Returns an explanation of why the matcher failed to match with +should_not+.
119
+ #
120
+ # @return [String]
121
+ def negative_failure_message
122
+ "expected document to not #{description}; got: #{@rendered}"
123
+ end
124
+
103
125
  # Answers whether or not the matcher matches any elements within +rendered+.
104
126
  #
105
127
  # @param [String] rendered A string of HTML or an Object whose +to_s+ method returns HTML.
@@ -107,7 +129,8 @@ module RSpec::TagMatchers
107
129
  #
108
130
  # @return [Boolean]
109
131
  def matches?(rendered)
110
- Nokogiri::HTML::Document.parse(rendered.to_s).css(@name).select do |element|
132
+ @rendered = rendered
133
+ Nokogiri::HTML::Document.parse(@rendered.to_s).css(@name).select do |element|
111
134
  matches_attributes?(element) && matches_criteria?(element)
112
135
  end.length > 0
113
136
  end
@@ -189,7 +212,6 @@ module RSpec::TagMatchers
189
212
 
190
213
  private
191
214
 
192
-
193
215
  # Answers whether or not +element+ matches the attributes in the attributes hash given to
194
216
  # {#with_attribute}.
195
217
  #
@@ -217,5 +239,85 @@ module RSpec::TagMatchers
217
239
  end
218
240
  end
219
241
  end
242
+
243
+ # Provides extra description that can be appended to the basic description.
244
+ #
245
+ # @return [String]
246
+ def extra_description
247
+ attributes_description
248
+ end
249
+
250
+ # Returns a description of the attribute criteria. For example, the description of an attribute
251
+ # criteria of <tt>with_attribute(:foo => "bar")</tt> will look like <tt>'with attribute
252
+ # foo="bar"'</tt>.
253
+ #
254
+ # @return [String]
255
+ def attributes_description
256
+ grouped_attributes = @attributes.group_by { |key, value| !!value }
257
+
258
+ make_sentence(
259
+ # Yes, this is functionally equivalent to grouped_attributes.map, except this forces the
260
+ # keys to be evalutated in the order [true, false]. This is necessary to maintain
261
+ # compatibility with Ruby 1.8.7, because hashes in 1.8.7 aren't ordered.
262
+ [true, false].reduce([]) do |memo, group_key|
263
+ attributes = grouped_attributes[group_key]
264
+
265
+ if attributes
266
+ memo << grouped_attributes_prefix(group_key, attributes.count > 1) +
267
+ grouped_attributes_description(attributes)
268
+ end
269
+
270
+ memo
271
+ end
272
+ )
273
+ end
274
+
275
+ # Provides a prefix that can be used before a list of attribute criteria. Possible oututs are
276
+ # <tt>"with attribute"</tt>, <tt>"with attributes"</tt>, <tt>"without attribute"</tt>, and
277
+ # <tt>"without attributes"</tt>.
278
+ #
279
+ # @param [Boolean] value Whether this should prefix inclusive attributes. Selects between
280
+ # <tt>"with"</tt> and <tt>"without"</tt>.
281
+ # @param [Boolean] plural Whether this prefixes multiple attributes. Selects between
282
+ # <tt>"attribute"</tt> and <tt>"attributes"</tt>.
283
+ #
284
+ # @return [String]
285
+ def grouped_attributes_prefix(value, plural)
286
+ (value ? "with " : "without ") + (plural ? "attributes " : "attribute ")
287
+ end
288
+
289
+ # Describes a group of attribute criteria, combining them into a sentence fragment, with
290
+ # punctuation and conjunctions if necessary.
291
+ #
292
+ # @param [Array] attributes A list of <tt>[key, value]</tt> pairs that describe the attribute
293
+ # criteria.
294
+ #
295
+ # @return [String]
296
+ def grouped_attributes_description(attributes)
297
+ make_sentence(
298
+ attributes.sort_by{ |key, value| key.to_s }.map do |key, value|
299
+ attribute_description(key, value)
300
+ end
301
+ )
302
+ end
303
+
304
+ # Returns a string describing the criteria for matching attribute +key+ with +value+.
305
+ #
306
+ # @param [Symbol] key The attribute's key.
307
+ # @param [Object] value The attribute's expected value.
308
+ #
309
+ # @return [String]
310
+ def attribute_description(key, value)
311
+ case value
312
+ when true
313
+ "#{key}=anything"
314
+ when false
315
+ key.to_s
316
+ when Regexp
317
+ "#{key}=~#{value.inspect}"
318
+ else
319
+ "#{key}=#{value.inspect}"
320
+ end
321
+ end
220
322
  end
221
323
  end
@@ -16,7 +16,45 @@ module RSpec::TagMatchers
16
16
 
17
17
  # Initializes a HasTimeSelect matcher.
18
18
  def initialize
19
- super(4 => HasSelect.new, 5 => HasSelect.new)
19
+ super('4i' => HasSelect.new, '5i' => HasSelect.new)
20
20
  end
21
+
22
+ # Returns a description of the matcher's criteria.
23
+ #
24
+ # @return [String]
25
+ def description
26
+ [basic_description, extra_description].compact.join(" ")
27
+ end
28
+
29
+ # Returns an explanation of why the matcher failed to match with +should+.
30
+ #
31
+ # @return [String]
32
+ def failure_message
33
+ "expected document to #{description}; got: #{@rendered}"
34
+ end
35
+
36
+ # Returns an explanation of why the matcher failed to match with +should_not+.
37
+ #
38
+ # @return [String]
39
+ def negative_failure_message
40
+ "expected document to not #{description}; got: #{@rendered}"
41
+ end
42
+
43
+ private
44
+
45
+ # Returns a basic description.
46
+ #
47
+ # @return [String]
48
+ def basic_description
49
+ "have time select"
50
+ end
51
+
52
+ # Provides an extra description fragment that can be appended to the basic description.
53
+ #
54
+ # @return [String]
55
+ def extra_description
56
+ "for #{@for.join(".")}" if @for
57
+ end
58
+
21
59
  end
22
60
  end
@@ -0,0 +1,38 @@
1
+ module RSpec::TagMatchers::Helpers
2
+ module SentenceHelper
3
+ # Joins multiple strings into a sentence with punctuation and conjunctions.
4
+ #
5
+ # @example Forming sentences
6
+ # make_sentence("foo") # => "foo"
7
+ # make_sentence("foo", "bar") # => "foo and bar"
8
+ # make_sentence("foo", "bar", "baz") # => "foo, bar, and baz"
9
+ #
10
+ # @example Overriding the conjunction
11
+ # make_sentence("foo", "bar", "baz", :conjunction => "or") # => "foo, bar, or baz"
12
+ #
13
+ # @param [Strings] *strings A list of strings to be combined into a sentence. The last item
14
+ # can be an options hash.
15
+ #
16
+ # @option *strings.last [String] :conjunction ("and") The conjunction to use to join sentence
17
+ # fragments.
18
+ #
19
+ # @return [String]
20
+ def make_sentence(*strings)
21
+ options = strings.last.is_a?(Hash) ? strings.pop : {}
22
+ strings = strings.flatten.map(&:to_s).reject(&:empty?)
23
+ conjunction = options[:conjunction] || "and"
24
+
25
+ case strings.count
26
+ when 0
27
+ ""
28
+ when 1
29
+ strings.first
30
+ else
31
+ last = strings.pop
32
+ puncuation = strings.count > 1 ? ", " : " "
33
+
34
+ [strings, "#{conjunction} #{last}"].flatten.join(puncuation)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -6,9 +6,9 @@ module RSpec::TagMatchers
6
6
  #
7
7
  # @example Building a date matcher
8
8
  # matcher = MultipleInputMatcher.new(
9
- # 1 => HasSelect.new,
10
- # 2 => HasSelect.new,
11
- # 3 => HasSelect.new
9
+ # '1i' => HasSelect.new,
10
+ # '2i' => HasSelect.new,
11
+ # '3i' => HasSelect.new
12
12
  # )
13
13
  # matcher.for(:user => :birthday) # will match <select> tags with names
14
14
  # # "user[birthday(1i)]", "user[birthday(2i)]", and
@@ -16,21 +16,20 @@ module RSpec::TagMatchers
16
16
  #
17
17
  # @example Building a time matcher
18
18
  # MultipleInputMatcher.new( # by default, will match <select> tags with names by regular
19
- # 4 => HasSelect.new, # expressions: /\(4i\)/ and /\(5i\)/
20
- # 5 => HasSelect.new
19
+ # '4i' => HasSelect.new, # expressions: /\(4i\)/ and /\(5i\)/
20
+ # '5i' => HasSelect.new
21
21
  # )
22
- #
23
- #
24
22
  class MultipleInputMatcher
25
23
 
26
24
  # Initializes a matcher that matches multiple input elements.
27
25
  #
28
- # @param [Hash] components A hash of matchers. The keys serve as indices and the values are the
29
- # matchers that must be satisfied.
26
+ # @param [Hash] components A hash of matchers. The keys should be the keys used in Rails'
27
+ # multi-parameter assignment, e.g., <tt>"1i"</tt>, <tt>"2s"</tt>, etc,
28
+ # and the values are the matchers that must be satisfied.
30
29
  def initialize(components)
31
30
  @components = components
32
- @components.each do |index, matcher|
33
- matcher.with_attribute(:name => /\(#{index}i\)/)
31
+ @components.each do |key, matcher|
32
+ matcher.with_attribute(:name => /\(#{key}\)/)
34
33
  end
35
34
  end
36
35
 
@@ -43,6 +42,7 @@ module RSpec::TagMatchers
43
42
  #
44
43
  # @return [Boolean]
45
44
  def matches?(rendered)
45
+ @rendered = rendered
46
46
  @failures = matchers.reject do |matcher|
47
47
  matcher.matches?(rendered)
48
48
  end
@@ -50,14 +50,14 @@ module RSpec::TagMatchers
50
50
  @failures.empty?
51
51
  end
52
52
 
53
- # Specifies the inputs names with more accuracy than the default regular expressions. It
54
- # delegates to each matcher's +for+ method. But it first appends the matcher's index to the last
53
+ # Specifies the inputs' names with more accuracy than the default regular expressions. It
54
+ # delegates to each matcher's +for+ method. But it first appends the matcher's key to the last
55
55
  # component of the input's name.
56
56
  #
57
57
  # @example Input naming delegation
58
58
  # hour_matcher = HasSelect.new
59
59
  # minute_matcher = HasSelect.new
60
- # time_matcher = MultipleInputMatcher.new(4 => hour_matcher, 5 => minute_matcher)
60
+ # time_matcher = MultipleInputMatcher.new('4i' => hour_matcher, '5i' => minute_matcher)
61
61
  #
62
62
  # time_matcher.for(:event => :start_time) # calls hour_matcher.for("event", "start_time(4i)")
63
63
  # # and minute_matcher.for("event", "start_time(5i)")
@@ -66,8 +66,12 @@ module RSpec::TagMatchers
66
66
  #
67
67
  # @return [MultipleInputMatcher] self
68
68
  def for(*args)
69
+ @for = args.dup
70
+ @for.extend(DeepFlattening)
71
+ @for = @for.deep_flatten
72
+
69
73
  @components.each do |index, matcher|
70
- delegated_for(index, matcher, args)
74
+ delegated_for(index, matcher, @for)
71
75
  end
72
76
  self
73
77
  end
@@ -95,19 +99,16 @@ module RSpec::TagMatchers
95
99
  @components.values
96
100
  end
97
101
 
98
- # Set's +matcher+'s input name according to +args+ and +index+.
102
+ # Set's +matcher+'s input name according to +args+ and +key+.
99
103
  #
100
- # @param [Integer] index The matcher's index.
104
+ # @param [String] key The matcher's key.
101
105
  # @param [HasInput] matcher The matcher.
102
106
  # @param [Arrah, Hash] args A hierarchy of names that would normally be passed to
103
107
  # {HasInput#for}.
104
- def delegated_for(index, matcher, args)
105
- args = args.dup
106
- args.extend(DeepFlattening)
107
- args = args.deep_flatten
108
-
108
+ def delegated_for(key, matcher, args)
109
+ args = args.dup
109
110
  args[-1] = args[-1].to_s
110
- args[-1] += "(#{index}i)"
111
+ args[-1] += "(#{key})"
111
112
  matcher.for(*args)
112
113
  end
113
114
  end
@@ -2,6 +2,9 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..', 'core_ext'))
2
2
 
3
3
  module RSpec
4
4
  module TagMatchers
5
+ module Helpers
6
+ autoload :SentenceHelper, 'rspec/tag_matchers/helpers/sentence_helper'
7
+ end
5
8
  end
6
9
  end
7
10
 
@@ -11,3 +14,4 @@ require 'rspec/tag_matchers/has_input'
11
14
  require 'rspec/tag_matchers/has_checkbox'
12
15
  require 'rspec/tag_matchers/has_select'
13
16
  require 'rspec/tag_matchers/has_time_select'
17
+ require 'rspec/tag_matchers/has_date_select'
@@ -0,0 +1,93 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{rspec-tag_matchers}
8
+ s.version = "0.1.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = [%q{David Cuddeback}]
12
+ s.date = %q{2012-02-18}
13
+ s.description = %q{A collection of RSpec matchers that understand Rails conventions, allowing for more concise specs.}
14
+ s.email = %q{david.cuddeback@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".rspec",
22
+ ".travis.yml",
23
+ "Gemfile",
24
+ "Gemfile.lock",
25
+ "Guardfile",
26
+ "LICENSE.txt",
27
+ "README.rdoc",
28
+ "Rakefile",
29
+ "VERSION",
30
+ "core_ext/deep_flattening.rb",
31
+ "lib/rspec-tag_matchers.rb",
32
+ "lib/rspec/tag_matchers.rb",
33
+ "lib/rspec/tag_matchers/has_checkbox.rb",
34
+ "lib/rspec/tag_matchers/has_date_select.rb",
35
+ "lib/rspec/tag_matchers/has_form.rb",
36
+ "lib/rspec/tag_matchers/has_input.rb",
37
+ "lib/rspec/tag_matchers/has_select.rb",
38
+ "lib/rspec/tag_matchers/has_tag.rb",
39
+ "lib/rspec/tag_matchers/has_time_select.rb",
40
+ "lib/rspec/tag_matchers/helpers/sentence_helper.rb",
41
+ "lib/rspec/tag_matchers/multiple_input_matcher.rb",
42
+ "rspec-tag_matchers.gemspec",
43
+ "spec/core_ext/deep_flattening_spec.rb",
44
+ "spec/lib/rspec/tag_matchers/has_checkbox_spec.rb",
45
+ "spec/lib/rspec/tag_matchers/has_date_select_spec.rb",
46
+ "spec/lib/rspec/tag_matchers/has_form_spec.rb",
47
+ "spec/lib/rspec/tag_matchers/has_input_spec.rb",
48
+ "spec/lib/rspec/tag_matchers/has_select_spec.rb",
49
+ "spec/lib/rspec/tag_matchers/has_tag_spec.rb",
50
+ "spec/lib/rspec/tag_matchers/has_time_select_spec.rb",
51
+ "spec/lib/rspec/tag_matchers/multiple_input_matcher_spec.rb",
52
+ "spec/spec_helper.rb"
53
+ ]
54
+ s.homepage = %q{http://github.com/dcuddeback/rspec-tag_matchers}
55
+ s.licenses = [%q{MIT}]
56
+ s.require_paths = [%q{lib}]
57
+ s.rubygems_version = %q{1.8.6}
58
+ s.summary = %q{RSpec matchers for Rails views}
59
+
60
+ if s.respond_to? :specification_version then
61
+ s.specification_version = 3
62
+
63
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
64
+ s.add_runtime_dependency(%q<nokogiri>, [">= 0"])
65
+ s.add_development_dependency(%q<rspec>, [">= 0"])
66
+ s.add_development_dependency(%q<yard>, [">= 0"])
67
+ s.add_development_dependency(%q<bundler>, [">= 0"])
68
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
69
+ s.add_development_dependency(%q<guard>, [">= 0"])
70
+ s.add_development_dependency(%q<guard-rspec>, [">= 0"])
71
+ s.add_development_dependency(%q<guard-bundler>, [">= 0"])
72
+ else
73
+ s.add_dependency(%q<nokogiri>, [">= 0"])
74
+ s.add_dependency(%q<rspec>, [">= 0"])
75
+ s.add_dependency(%q<yard>, [">= 0"])
76
+ s.add_dependency(%q<bundler>, [">= 0"])
77
+ s.add_dependency(%q<jeweler>, [">= 0"])
78
+ s.add_dependency(%q<guard>, [">= 0"])
79
+ s.add_dependency(%q<guard-rspec>, [">= 0"])
80
+ s.add_dependency(%q<guard-bundler>, [">= 0"])
81
+ end
82
+ else
83
+ s.add_dependency(%q<nokogiri>, [">= 0"])
84
+ s.add_dependency(%q<rspec>, [">= 0"])
85
+ s.add_dependency(%q<yard>, [">= 0"])
86
+ s.add_dependency(%q<bundler>, [">= 0"])
87
+ s.add_dependency(%q<jeweler>, [">= 0"])
88
+ s.add_dependency(%q<guard>, [">= 0"])
89
+ s.add_dependency(%q<guard-rspec>, [">= 0"])
90
+ s.add_dependency(%q<guard-bundler>, [">= 0"])
91
+ end
92
+ end
93
+
@@ -0,0 +1,196 @@
1
+ require 'spec_helper'
2
+
3
+ describe RSpec::TagMatchers::HasDateSelect do
4
+ include RSpec::TagMatchers
5
+
6
+ # single inputs for start_date
7
+ let(:start_date_year) { "<select name='event[start_date(1i)]'></select>" }
8
+ let(:start_date_month) { "<select name='event[start_date(2i)]'></select>" }
9
+ let(:start_date_day) { "<select name='event[start_date(3i)]'></select>" }
10
+
11
+ # single inputs for end_date
12
+ let(:end_date_year) { "<select name='event[end_date(1i)]'></select>" }
13
+ let(:end_date_month) { "<select name='event[end_date(2i)]'></select>" }
14
+ let(:end_date_day) { "<select name='event[end_date(3i)]'></select>" }
15
+
16
+ # hidden inputs
17
+ let(:hidden_start_date_year) { "<input type='hidden' name='event[start_date(1i)]' />" }
18
+ let(:hidden_start_date_month) { "<input type='hidden' name='event[start_date(2i)]' />" }
19
+ let(:hidden_start_date_day) { "<input type='hidden' name='event[start_date(3i)]' />" }
20
+ let(:hidden_other) { "<input type='hidden' name='foo' />" }
21
+
22
+ # combined inputs for start_date
23
+ let(:start_date_with_year_month_and_day) { start_date_year + start_date_month + start_date_day }
24
+ let(:start_date_with_year_and_month) { start_date_year + start_date_month }
25
+
26
+ # combined inputs for end_date
27
+ let(:end_date_with_year_and_month) { end_date_year + end_date_month }
28
+ let(:end_date_with_year_month_and_day) { end_date_year + end_date_month + end_date_day }
29
+
30
+ # combined inputs for start_date with hidden inputs
31
+ let(:start_date_with_year_and_month_and_hidden_other) { start_date_year + start_date_month + hidden_other }
32
+ let(:start_date_with_year_and_month_and_hidden_day) { start_date_year + start_date_month + hidden_start_date_day }
33
+ let(:start_date_with_year_and_hidden_month_and_day) { start_date_year + hidden_start_date_month + start_date_day }
34
+ let(:start_date_with_hidden_year_and_month_and_day) { hidden_start_date_year + start_date_month + start_date_day }
35
+ let(:start_date_with_year_and_hidden_month_and_hidden_day) { start_date_year + hidden_start_date_month + hidden_start_date_day }
36
+
37
+ describe "date select matching" do
38
+ context "have_date_select" do
39
+ subject { have_date_select }
40
+
41
+ it { should match(start_date_with_year_month_and_day) }
42
+ it { should_not match(start_date_with_year_and_month) }
43
+ it { should_not match(start_date_year) }
44
+ it { should_not match(start_date_month) }
45
+ it { should_not match(start_date_day) }
46
+
47
+ it { should match(end_date_with_year_month_and_day) }
48
+ it { should_not match(end_date_with_year_and_month) }
49
+ it { should_not match(end_date_year) }
50
+ it { should_not match(end_date_month) }
51
+ it { should_not match(end_date_day) }
52
+ end
53
+ end
54
+
55
+ describe "matching discarded date parts" do
56
+ context "have_date_select.discard(:day)" do
57
+ subject { have_date_select.discard(:day) }
58
+
59
+ it { should match(start_date_with_year_and_month_and_hidden_day) }
60
+ it { should_not match(start_date_with_year_and_month_and_hidden_other) }
61
+ it { should_not match(start_date_with_year_month_and_day) }
62
+ it { should_not match(start_date_with_year_and_month) }
63
+ end
64
+
65
+ context "have_date_select.discard(:month)" do
66
+ subject { have_date_select.discard(:month) }
67
+
68
+ it { should match(start_date_with_year_and_hidden_month_and_day) }
69
+ it { should_not match(start_date_with_year_month_and_day) }
70
+ end
71
+
72
+ context "have_date_select.discard(:year)" do
73
+ subject { have_date_select.discard(:year) }
74
+
75
+ it { should match(start_date_with_hidden_year_and_month_and_day) }
76
+ it { should_not match(start_date_with_year_month_and_day) }
77
+ end
78
+
79
+ context "have_date_select.discard(:month, :day)" do
80
+ subject { have_date_select.discard(:month, :day) }
81
+
82
+ it { should match(start_date_with_year_and_hidden_month_and_hidden_day) }
83
+ it { should_not match(start_date_with_year_month_and_day) }
84
+ end
85
+ end
86
+
87
+ describe "matching input names" do
88
+ context "have_date_select.for(:event => :start_date)" do
89
+ subject { have_date_select.for(:event => :start_date) }
90
+
91
+ it { should match(start_date_with_year_month_and_day) }
92
+ it { should_not match(start_date_with_year_and_month) }
93
+
94
+ it { should_not match(end_date_with_year_and_month) }
95
+ it { should_not match(end_date_with_year_month_and_day) }
96
+ end
97
+
98
+ context "have_date_select.for(:event => :end_date)" do
99
+ subject { have_date_select.for(:event => :end_date) }
100
+
101
+ it { should match(end_date_with_year_month_and_day) }
102
+ it { should_not match(end_date_with_year_and_month) }
103
+
104
+ it { should_not match(start_date_with_year_and_month) }
105
+ it { should_not match(start_date_with_year_month_and_day) }
106
+ end
107
+ end
108
+
109
+ describe "#description" do
110
+ context "for simple date select" do
111
+ context "have_date_select" do
112
+ subject { have_date_select.description }
113
+ it { should == "have date select" }
114
+ end
115
+ end
116
+
117
+ context "for date select with attribute matcher" do
118
+ context "have_date_select.for(:start_date)" do
119
+ subject { have_date_select.for(:start_date).description }
120
+ it { should == "have date select for start_date" }
121
+ end
122
+
123
+ context "have_date_select.for(:event => :start_date)" do
124
+ subject { have_date_select.for(:event => :start_date).description }
125
+ it { should == "have date select for event.start_date" }
126
+ end
127
+
128
+ context "have_date_select.for(:event, :start => :date)" do
129
+ subject { have_date_select.for(:event, :start => :date).description }
130
+ it { should == "have date select for event.start.date" }
131
+ end
132
+ end
133
+
134
+ context "for date select with discarded components" do
135
+ context "have_date_select.discard(:day)" do
136
+ subject { have_date_select.discard(:day).description }
137
+ it { should == "have date select without day" }
138
+ end
139
+
140
+ context "have_date_select.discard(:month)" do
141
+ subject { have_date_select.discard(:month).description }
142
+ it { should == "have date select without month" }
143
+ end
144
+
145
+ context "have_date_select.discard(:year)" do
146
+ subject { have_date_select.discard(:year).description }
147
+ it { should == "have date select without year" }
148
+ end
149
+
150
+ context "have_date_select.discard(:day, :month)" do
151
+ subject { have_date_select.discard(:day, :month).description }
152
+ it { should == "have date select without day or month" }
153
+ end
154
+
155
+ context "have_date_select.discard(:day, :month, :year)" do
156
+ subject { have_date_select.discard(:day, :month, :year).description }
157
+ it { should == "have date select without day, month, or year" }
158
+ end
159
+ end
160
+
161
+ context "for date select with attribute matcher and discarded components" do
162
+ context "have_date_select.discard(:day).for(:event => :start_date)" do
163
+ subject { have_date_select.discard(:day).for(:event => :start_date).description }
164
+ it { should == "have date select for event.start_date without day" }
165
+ end
166
+ end
167
+ end
168
+
169
+ describe "#failure_message" do
170
+ context "for have_date_select" do
171
+ let(:matcher) { have_date_select }
172
+
173
+ context "matching '<bar></bar>'" do
174
+ let(:document) { "<bar></bar>" }
175
+
176
+ before { matcher.matches?(document) }
177
+ subject { matcher.failure_message }
178
+ it { should == "expected document to #{matcher.description}; got: #{document}" }
179
+ end
180
+ end
181
+ end
182
+
183
+ describe "#negative_failure_message" do
184
+ context "for have_date_select" do
185
+ let(:matcher) { have_date_select }
186
+
187
+ context "matching \"<select name='(4i)'><select><select name='(5i)'></select>\"" do
188
+ let(:document) { "<select name='(4i)'><select><select name='(5i)'></select>" }
189
+
190
+ before { matcher.matches?(document) }
191
+ subject { matcher.negative_failure_message }
192
+ it { should == "expected document to not #{matcher.description}; got: #{document}" }
193
+ end
194
+ end
195
+ end
196
+ end
@@ -50,4 +50,13 @@ describe RSpec::TagMatchers::HasInput do
50
50
  it { should match("<input name='user[name][first]' />") }
51
51
  end
52
52
  end
53
+
54
+ describe "matching input values" do
55
+ context "have_input.value(:foo)" do
56
+ subject { have_input.value(:foo) }
57
+ it { should match("<input value='foo' />") }
58
+ it { should_not match("<input value='bar' />") }
59
+ it { should_not match("<input />") }
60
+ end
61
+ end
53
62
  end
@@ -291,4 +291,88 @@ describe RSpec::TagMatchers::HasTag do
291
291
  end
292
292
  end
293
293
  end
294
+
295
+ describe "#description" do
296
+ context "for simple matchers" do
297
+ context "have_tag(:foo)" do
298
+ subject { have_tag(:foo) }
299
+ its(:description) { should == 'have "foo" tag' }
300
+ end
301
+
302
+ context "have_tag(:bar)" do
303
+ subject { have_tag(:bar) }
304
+ its(:description) { should == 'have "bar" tag' }
305
+ end
306
+ end
307
+
308
+ context "for matchers with attribute criteria" do
309
+ context "have_tag(:foo).with_attribute(:bar => :baz)" do
310
+ subject { have_tag(:foo).with_attribute(:bar => :baz) }
311
+ its(:description) { should == 'have "foo" tag with attribute bar=:baz' }
312
+ end
313
+
314
+ context "have_tag(:foo).with_attribute(:bar => 'baz')" do
315
+ subject { have_tag(:foo).with_attribute(:bar => 'baz') }
316
+ its(:description) { should == 'have "foo" tag with attribute bar="baz"' }
317
+ end
318
+
319
+ context "have_tag(:foo).with_attribute(:bar => /baz/)" do
320
+ subject { have_tag(:foo).with_attribute(:bar => /baz/) }
321
+ its(:description) { should == 'have "foo" tag with attribute bar=~/baz/' }
322
+ end
323
+
324
+ context "have_tag(:foo).with_attribute(:bar => true)" do
325
+ subject { have_tag(:foo).with_attribute(:bar => true) }
326
+ its(:description) { should == 'have "foo" tag with attribute bar=anything' }
327
+ end
328
+
329
+ context "have_tag(:foo).with_attribute(:bar => false)" do
330
+ subject { have_tag(:foo).with_attribute(:bar => false) }
331
+ its(:description) { should == 'have "foo" tag without attribute bar' }
332
+ end
333
+
334
+ context "have_tag(:foo).with_attributes(:bar => '1', :baz => '2')" do
335
+ subject { have_tag(:foo).with_attributes(:bar => '1', :baz => '2') }
336
+ its(:description) { should == 'have "foo" tag with attributes bar="1" and baz="2"' }
337
+ end
338
+
339
+ context "have_tag(:foo).with_attributes(:bar => '1', :baz => '2', :qux => '3')" do
340
+ subject { have_tag(:foo).with_attributes(:bar => '1', :baz => '2', :qux => '3') }
341
+ its(:description) { should == 'have "foo" tag with attributes bar="1", baz="2", and qux="3"' }
342
+ end
343
+
344
+ context "have_tag(:foo).with_attributes(:bar => true, :baz => false)" do
345
+ subject { have_tag(:foo).with_attributes(:bar => true, :baz => false) }
346
+ its(:description) { should == 'have "foo" tag with attribute bar=anything and without attribute baz' }
347
+ end
348
+ end
349
+ end
350
+
351
+ describe "#failure_message" do
352
+ context "have_tag(:foo)" do
353
+ let(:matcher) { have_tag(:foo) }
354
+
355
+ context "matching '<bar></bar>'" do
356
+ let(:document) { "<bar></bar>" }
357
+
358
+ before { matcher.matches?(document) }
359
+ subject { matcher.failure_message }
360
+ it { should == "expected document to #{matcher.description}; got: #{document}" }
361
+ end
362
+ end
363
+ end
364
+
365
+ describe "#negative_failure_message" do
366
+ context "have_tag(:foo)" do
367
+ let(:matcher) { have_tag(:foo) }
368
+
369
+ context "matching '<foo></foo>'" do
370
+ let(:document) { "<foo></foo>" }
371
+
372
+ before { matcher.matches?(document) }
373
+ subject { matcher.negative_failure_message }
374
+ it { should == "expected document to not #{matcher.description}; got: #{document}" }
375
+ end
376
+ end
377
+ end
294
378
  end
@@ -56,4 +56,54 @@ describe RSpec::TagMatchers::HasTimeSelect do
56
56
  it { should_not match(start_time_with_hour_minute_and_second) }
57
57
  end
58
58
  end
59
+
60
+ describe "#description" do
61
+ context "for have_time_select" do
62
+ subject { have_time_select.description }
63
+ it { should == "have time select" }
64
+ end
65
+
66
+ context "for have_time_select.for(:start_time)" do
67
+ subject { have_time_select.for(:start_time).description }
68
+ it { should == "have time select for start_time" }
69
+ end
70
+
71
+ context "for have_time_select.for(:event => :start_time)" do
72
+ subject { have_time_select.for(:event => :start_time).description }
73
+ it { should == "have time select for event.start_time" }
74
+ end
75
+
76
+ context "for have_time_select.for(:event, :start => :time)" do
77
+ subject { have_time_select.for(:event, :start => :time).description }
78
+ it { should == "have time select for event.start.time" }
79
+ end
80
+ end
81
+
82
+ describe "#failure_message" do
83
+ context "for have_time_select" do
84
+ let(:matcher) { have_time_select }
85
+
86
+ context "matching '<bar></bar>'" do
87
+ let(:document) { "<bar></bar>" }
88
+
89
+ before { matcher.matches?(document) }
90
+ subject { matcher.failure_message }
91
+ it { should == "expected document to #{matcher.description}; got: #{document}" }
92
+ end
93
+ end
94
+ end
95
+
96
+ describe "#negative_failure_message" do
97
+ context "for have_time_select" do
98
+ let(:matcher) { have_time_select }
99
+
100
+ context "matching \"<select name='(4i)'><select><select name='(5i)'></select>\"" do
101
+ let(:document) { "<select name='(4i)'><select><select name='(5i)'></select>" }
102
+
103
+ before { matcher.matches?(document) }
104
+ subject { matcher.negative_failure_message }
105
+ it { should == "expected document to not #{matcher.description}; got: #{document}" }
106
+ end
107
+ end
108
+ end
59
109
  end
@@ -6,7 +6,7 @@ describe RSpec::TagMatchers::MultipleInputMatcher do
6
6
  let(:html) { "<input name='foo' /><input name='bar' />" }
7
7
 
8
8
  let(:multiple_matcher) do
9
- RSpec::TagMatchers::MultipleInputMatcher.new(2 => foo_matcher, 3 => bar_matcher)
9
+ RSpec::TagMatchers::MultipleInputMatcher.new('2i' => foo_matcher, '3i' => bar_matcher)
10
10
  end
11
11
 
12
12
  before do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-tag_matchers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-11-16 00:00:00.000000000Z
12
+ date: 2012-02-18 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: nokogiri
16
- requirement: &2161366360 !ruby/object:Gem::Requirement
16
+ requirement: &2154865400 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2161366360
24
+ version_requirements: *2154865400
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &2161365880 !ruby/object:Gem::Requirement
27
+ requirement: &2154864620 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *2161365880
35
+ version_requirements: *2154864620
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: yard
38
- requirement: &2161365260 !ruby/object:Gem::Requirement
38
+ requirement: &2154864000 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *2161365260
46
+ version_requirements: *2154864000
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: bundler
49
- requirement: &2161364780 !ruby/object:Gem::Requirement
49
+ requirement: &2154863260 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *2161364780
57
+ version_requirements: *2154863260
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: jeweler
60
- requirement: &2161364300 !ruby/object:Gem::Requirement
60
+ requirement: &2154862600 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '0'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *2161364300
68
+ version_requirements: *2154862600
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: guard
71
- requirement: &2161363660 !ruby/object:Gem::Requirement
71
+ requirement: &2154861880 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: '0'
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *2161363660
79
+ version_requirements: *2154861880
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: guard-rspec
82
- requirement: &2161363180 !ruby/object:Gem::Requirement
82
+ requirement: &2154861180 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,10 +87,10 @@ dependencies:
87
87
  version: '0'
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *2161363180
90
+ version_requirements: *2154861180
91
91
  - !ruby/object:Gem::Dependency
92
92
  name: guard-bundler
93
- requirement: &2161362700 !ruby/object:Gem::Requirement
93
+ requirement: &2154859720 !ruby/object:Gem::Requirement
94
94
  none: false
95
95
  requirements:
96
96
  - - ! '>='
@@ -98,7 +98,7 @@ dependencies:
98
98
  version: '0'
99
99
  type: :development
100
100
  prerelease: false
101
- version_requirements: *2161362700
101
+ version_requirements: *2154859720
102
102
  description: A collection of RSpec matchers that understand Rails conventions, allowing
103
103
  for more concise specs.
104
104
  email: david.cuddeback@gmail.com
@@ -122,14 +122,18 @@ files:
122
122
  - lib/rspec-tag_matchers.rb
123
123
  - lib/rspec/tag_matchers.rb
124
124
  - lib/rspec/tag_matchers/has_checkbox.rb
125
+ - lib/rspec/tag_matchers/has_date_select.rb
125
126
  - lib/rspec/tag_matchers/has_form.rb
126
127
  - lib/rspec/tag_matchers/has_input.rb
127
128
  - lib/rspec/tag_matchers/has_select.rb
128
129
  - lib/rspec/tag_matchers/has_tag.rb
129
130
  - lib/rspec/tag_matchers/has_time_select.rb
131
+ - lib/rspec/tag_matchers/helpers/sentence_helper.rb
130
132
  - lib/rspec/tag_matchers/multiple_input_matcher.rb
133
+ - rspec-tag_matchers.gemspec
131
134
  - spec/core_ext/deep_flattening_spec.rb
132
135
  - spec/lib/rspec/tag_matchers/has_checkbox_spec.rb
136
+ - spec/lib/rspec/tag_matchers/has_date_select_spec.rb
133
137
  - spec/lib/rspec/tag_matchers/has_form_spec.rb
134
138
  - spec/lib/rspec/tag_matchers/has_input_spec.rb
135
139
  - spec/lib/rspec/tag_matchers/has_select_spec.rb
@@ -152,7 +156,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
152
156
  version: '0'
153
157
  segments:
154
158
  - 0
155
- hash: -4097826557927416291
159
+ hash: 95040879081577537
156
160
  required_rubygems_version: !ruby/object:Gem::Requirement
157
161
  none: false
158
162
  requirements: