rspec-tag_matchers 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,7 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
6
+ --protected
7
+ --name-tag modifier:Modifiers
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ bundler_args: "--without osx"
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - ree
7
+ - rbx-2.0
8
+ - jruby
data/Gemfile ADDED
@@ -0,0 +1,21 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "nokogiri"
4
+
5
+ group :development do
6
+ gem "rspec"
7
+ gem "yard"
8
+ gem "bundler"
9
+ gem "jeweler"
10
+ gem "guard"
11
+ gem "guard-rspec"
12
+ gem "guard-bundler"
13
+ end
14
+
15
+ group :osx do
16
+ # Needed for Guard to operate optimally on OSX
17
+ platforms :mri do
18
+ gem 'rb-fsevent'
19
+ gem 'growl_notify'
20
+ end
21
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,49 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.3)
5
+ git (1.2.5)
6
+ growl_notify (0.0.3)
7
+ rb-appscript
8
+ guard (0.8.8)
9
+ thor (~> 0.14.6)
10
+ guard-bundler (0.1.3)
11
+ bundler (>= 1.0.0)
12
+ guard (>= 0.2.2)
13
+ guard-rspec (0.5.2)
14
+ guard (>= 0.8.4)
15
+ jeweler (1.6.4)
16
+ bundler (~> 1.0)
17
+ git (>= 1.2.5)
18
+ rake
19
+ nokogiri (1.5.0)
20
+ nokogiri (1.5.0-java)
21
+ rake (0.9.2.2)
22
+ rb-appscript (0.6.1)
23
+ rb-fsevent (0.4.3.1)
24
+ rspec (2.7.0)
25
+ rspec-core (~> 2.7.0)
26
+ rspec-expectations (~> 2.7.0)
27
+ rspec-mocks (~> 2.7.0)
28
+ rspec-core (2.7.1)
29
+ rspec-expectations (2.7.0)
30
+ diff-lcs (~> 1.1.2)
31
+ rspec-mocks (2.7.0)
32
+ thor (0.14.6)
33
+ yard (0.7.3)
34
+
35
+ PLATFORMS
36
+ java
37
+ ruby
38
+
39
+ DEPENDENCIES
40
+ bundler
41
+ growl_notify
42
+ guard
43
+ guard-bundler
44
+ guard-rspec
45
+ jeweler
46
+ nokogiri
47
+ rb-fsevent
48
+ rspec
49
+ yard
data/Guardfile ADDED
@@ -0,0 +1,12 @@
1
+ # vim: filetype=ruby
2
+
3
+ guard 'bundler' do
4
+ watch('Gemfile')
5
+ end
6
+
7
+ guard 'rspec', :version => 2, :all_on_start => true, :all_after_pass => true do
8
+ watch(%r{^spec/.+_spec\.rb$})
9
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
10
+ watch(%r{^core_ext/(.+)\.rb$}) { |m| "spec/core_ext/#{m[1]}_spec.rb" }
11
+ watch("spec/spec_helper.rb") { "spec" }
12
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 David Cuddeback
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 PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL 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.rdoc ADDED
@@ -0,0 +1,45 @@
1
+ = rspec-tag_matchers
2
+
3
+ <tt>rspec-tag_matchers</tt> is a collection of RSpec matchers for testing Rails views. The matchers
4
+ can match against any string containing HTML or any object whose +to_s+ method returns a string
5
+ containing HTML (this includes Nokogiri classes). They assume Rails conventions. For example, the
6
+ +has_form+ matcher looks for a hidden input named +_method+ when testing a form's method.
7
+
8
+ == Example
9
+
10
+ describe "users/new.html.haml" do
11
+ before(:each) do
12
+ assign(:user, User.new)
13
+ render
14
+ end
15
+
16
+ it { should have_form.with_verb(:post).with_action(user_path) }
17
+ it { should have_checkbox.for(:user => :terms_of_service) }
18
+ end
19
+
20
+ == Status
21
+
22
+ <tt>rspec-tag_matchers</tt> is tested against Ruby 1.8.7, 1.9.2, 1.9.3, REE,
23
+ Rubinius (1.8 mode) and JRuby.
24
+
25
+ {<img src="http://travis-ci.org/dcuddeback/rspec-tag_matchers.png?branch=master" />}[http://travis-ci.org/dcuddeback/rspec-tag_matchers]
26
+
27
+ The library is well tested, but the collection of matchers is incomplete. I'm adding more on an
28
+ as-needed basis. Please feel free to fork this repository to add your own then send me a pull
29
+ request.
30
+
31
+ == Contributing to rspec-tag_matchers
32
+
33
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
34
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
35
+ * Fork the project
36
+ * Start a feature/bugfix branch
37
+ * Commit and push until you are happy with your contribution
38
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
39
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
40
+
41
+ == Copyright
42
+
43
+ Copyright (c) 2011 David Cuddeback. See LICENSE.txt for
44
+ further details.
45
+
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "rspec-tag_matchers"
18
+ gem.homepage = "http://github.com/dcuddeback/rspec-tag_matchers"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{RSpec matchers for Rails views}
21
+ gem.description = %Q{A collection of RSpec matchers that understand Rails conventions, allowing for more concise specs.}
22
+ gem.email = "david.cuddeback@gmail.com"
23
+ gem.authors = ["David Cuddeback"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ task :default => :spec
35
+
36
+ require 'yard'
37
+ YARD::Rake::YardocTask.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,37 @@
1
+ # Mixin for Array and Hash objects that allows deep flattening of nested, heterogeneous data
2
+ # structures. Since it is only intended for internal use within this library, it is meant to extend
3
+ # existing Array or Hash objects instead of being included in the Array or Hash classes. This avoids
4
+ # inconsistencies between test and production environments by not affecting hashes or arrays in the
5
+ # user's codebase.
6
+ #
7
+ # @example
8
+ # hash = {:foo => {:bar => [1, [2], :baz]}}
9
+ # hash.respond_to?(:deep_flatten) # => false
10
+ # hash.extend(DeepFlattening)
11
+ # hash.respond_to?(:deep_flatten) # => true
12
+ #
13
+ # hash.deep_flatten # => [:foo, :bar, 1, 2, :baz]
14
+ #
15
+ # array = [1, [2, {:foo => {:bar => [3, 4]}}, 5]
16
+ # array.respond_to?(:deep_flatten) # => false
17
+ # array.extend(DeepFlattening)
18
+ # array.respond_to?(:deep_flatten) # => true
19
+ #
20
+ # array.deep_flatten # => [1, 2, :foo, :bar, 3, 4, 5]
21
+ module DeepFlattening
22
+ # Flattens a nested, heterogeneous data structure to an array.
23
+ def deep_flatten
24
+ # Could be called on an array or a hash. Hashes in 1.9 have a flatten
25
+ # method, but hashes in 1.8 do not. Therefor, we must call Hash#to_a before
26
+ # we can flatten the hash in a compatible way.
27
+ to_a.flatten.map do |object|
28
+ case object
29
+ when Array, Hash
30
+ object.extend(DeepFlattening)
31
+ object.deep_flatten
32
+ else
33
+ object
34
+ end
35
+ end.flatten
36
+ end
37
+ end
@@ -0,0 +1,60 @@
1
+ module RSpec::TagMatchers
2
+ # Matches <tt><input type="checkbox"></tt> tags in HTML.
3
+ #
4
+ # @modifier checked
5
+ # Specifies that the checkbox must be checked.
6
+ #
7
+ # @modifier not_checked
8
+ # Specifies that the checkbox must *not* be checked.
9
+ #
10
+ # @example Matching a checked checkbox for +terms_of_service+
11
+ # it { should have_checkbox.for(:terms_of_service).checked }
12
+ #
13
+ # @example Matching an unchecked checkbox for +terms_of_service+
14
+ # it { should have_checkbox.for(:terms_of_service).not_checked }
15
+ #
16
+ # @return [HasCheckbox]
17
+ #
18
+ # @see HasCheckbox.checked
19
+ # @see HasCheckbox.not_checked
20
+ def have_checkbox
21
+ HasCheckbox.new
22
+ end
23
+
24
+ # A matcher that matches <tt><input type="checkbox"></tt> tags.
25
+ class HasCheckbox < HasInput
26
+
27
+ # Initializes a HasCheckbox matcher that matches elements named +input+ with a +type+ attribute
28
+ # of +checkbox+.
29
+ def initialize
30
+ super
31
+ with_attribute(:type => :checkbox)
32
+ end
33
+
34
+ # Specifies that the checkbox must be selected. A checkbox is considered to be checked if it has
35
+ # a +checked+ attribute.
36
+ #
37
+ # @example Checkboxes which are considered checked
38
+ # <input type="checkbox" checked="checked" />
39
+ # <input type="checkbox" checked="1" />
40
+ # <input type="checkbox" checked />
41
+ #
42
+ # @return [HasCheckbox] self
43
+ def checked
44
+ with_attribute(:checked => true)
45
+ self
46
+ end
47
+
48
+ # Specifies that the checkbox must *not* be selected. A checkbox is not checked if it has no
49
+ # +checked+ attribute.
50
+ #
51
+ # @example An unchecked checkbox
52
+ # <input type="checkbox" />
53
+ #
54
+ # @return [HasCheckbox] self
55
+ def not_checked
56
+ with_attribute(:checked => false)
57
+ self
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,84 @@
1
+ module RSpec::TagMatchers
2
+ # Matches +<form>+ tags in HTML.
3
+ #
4
+ # @modifier with_verb
5
+ # Adds a criteria that the form must have the given HTTP verb.
6
+ #
7
+ # @modifier with_action
8
+ # Adds a criteria that the form must target the given URL as its action.
9
+ #
10
+ # @example Matching a simple form
11
+ # it { should have_form.with_verb(:post).with_action("/signup") }
12
+ #
13
+ # @example Matching a form with an overridden method
14
+ # it { should have_form.with_verb(:put) }
15
+ #
16
+ # @return [HasForm]
17
+ #
18
+ # @see HasForm#with_verb
19
+ # @see HasForm#with_action
20
+ def have_form
21
+ HasForm.new
22
+ end
23
+
24
+ # A matcher that matches +<form>+ tags.
25
+ class HasForm < HasTag
26
+
27
+ # Initializes a HasForm matcher that matches +form+ elements.
28
+ def initialize
29
+ super(:form)
30
+ end
31
+
32
+ # Specifies that the form will use the given HTTP verb, following Rails conventions. It first
33
+ # looks for a method override value (a hidden input named +_method+). If the method override
34
+ # value doesn't exist, then it looks for the +method+ attribute on the +form+ tag. Whichever
35
+ # value it finds will be compared to the value passed in as the +verb+ argument to +with_verb+.
36
+ #
37
+ # @param [Symbol] verb An HTTP verb, e.g., :get, :post, :put, or :delete.
38
+ #
39
+ # @return [HasForm] self
40
+ def with_verb(verb)
41
+ with_criteria do |element|
42
+ matches_verb?(element, verb)
43
+ end
44
+ end
45
+ alias :with_method :with_verb
46
+
47
+ # Specifies that the form must target the given URL as its action. It compares the +action+
48
+ # attribute on the +form+ tag to the +action+ argument passed to +with_action+.
49
+ #
50
+ # @param [String, Regexp] action The URL that the form should target.
51
+ #
52
+ # @return [HasForm] self
53
+ def with_action(action)
54
+ with_attribute(:action => action)
55
+ end
56
+
57
+ private
58
+
59
+ # Tests whether +element+ will use the given HTTP +verb+.
60
+ #
61
+ # @param [Nokogiri::XML::Node] element A +form+ element to be tested.
62
+ # @param [Symbol] verb An HTTP verb (:get, :post, :put, etc).
63
+ #
64
+ # @return [Boolean]
65
+ def matches_verb?(element, verb)
66
+ test_attribute(form_method(element), verb)
67
+ end
68
+
69
+ # Returns the attribute that specifies which HTTP verb the form will use. It considers a method
70
+ # override value as well as the +form+ tag's +method+ attribute.
71
+ #
72
+ # @param [Nokogiri::XML::Node] element A +form+ element to be tested.
73
+ #
74
+ # @return [Nokogiri::XML::Attr] The attribute that specifies the HTTP verb.
75
+ def form_method(element)
76
+ method_override(element) || element.attribute("method")
77
+ end
78
+
79
+ def method_override(element)
80
+ override = element.css("input[type=hidden][name=_method]")
81
+ override && override.first && override.attribute("value")
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,111 @@
1
+ require 'deep_flattening'
2
+
3
+ module RSpec::TagMatchers
4
+ # Matches +<input>+ tags in HTML.
5
+ #
6
+ # @modifier for
7
+ # Adds a criteria that the input must be for the given attribute.
8
+ #
9
+ # @example Matching an input for the +name+ attribute on +user+
10
+ # it { should have_input.for(:user => :name) }
11
+ # it { should have_input.for(:user, :name) }
12
+ #
13
+ # @return [HasInput]
14
+ #
15
+ # @see HasInput#for
16
+ def have_input
17
+ HasInput.new
18
+ end
19
+
20
+ # A base class for form input matchers.
21
+ #
22
+ # == Subclassing
23
+ #
24
+ # HasInput is intended to be subclassed to create more expressive matchers for specific types of
25
+ # form inputs. The helper methods in HasInput, e.g., {#for}, are intended to be useful for all
26
+ # types of form inputs.
27
+ #
28
+ # By default, HasInput matches +<input>+ elements, but this can be overridden by subclasses, e.g.,
29
+ # HasSelect matches +<select>+ elements. This can be done in the matcher's constructor:
30
+ #
31
+ # class HasSelect < HasInput
32
+ # def initialize
33
+ # super(:select)
34
+ # end
35
+ # end
36
+ #
37
+ # Some matchers might want to add more criteria inside their constructors. For example, matchers
38
+ # that match specific types of +<input>+ tags will want to add a criteria for matching the +type+
39
+ # attribute:
40
+ #
41
+ # class HasCheckbox < HasInput
42
+ # def initialize
43
+ # super(:checkbox)
44
+ # with_attribute(:type => :checkbox)
45
+ # end
46
+ # end
47
+ #
48
+ #
49
+ class HasInput < HasTag
50
+
51
+ # Initializes a HasInput matcher that matches elements named +name+.
52
+ #
53
+ # @param [Symbol] name The type of HTML tag to match.
54
+ def initialize(name = :input)
55
+ super
56
+ end
57
+
58
+ # Adds a criteria that the input tag should be for a given attribute.
59
+ #
60
+ # HasInput provides the {#for} modifier to all of its subclasses, which is useful for matching
61
+ # the input's name with Rails conventions. For example, a Rails template that uses +form_for+
62
+ # might output HTML that looks like this:
63
+ #
64
+ # <form method="POST" action="/users">
65
+ # <input type="text" name="user[name]" />
66
+ # </form>
67
+ #
68
+ # Instead of writing:
69
+ #
70
+ # it { should have_input.with_attribute(:name => "user[name]") }
71
+ #
72
+ # the user can write a more concise spec using {#for}:
73
+ #
74
+ # it { should have_input.for(:user => :name) }
75
+ #
76
+ # @example Match an input for the +name+ attribute of +user+
77
+ # it { should have_input.for(:user => :name) }
78
+ #
79
+ # @example Match an input for a nested attribute
80
+ # it { should have_input.for(:user => {:role => :admin}) }
81
+ # it { should have_input.for(:user, :role => :admin) }
82
+ #
83
+ # @param [Array, Hash] args A hierarchy of strings that specifiy the attribute name.
84
+ #
85
+ # @return [HasInput] self
86
+ def for(*args)
87
+ with_attribute(:name => build_name(*args))
88
+ self
89
+ end
90
+
91
+ private
92
+
93
+ # Converts an array or hash of names to a name for an input form.
94
+ #
95
+ # @example
96
+ # build_name(:foo => :bar) # => "foo[bar]"
97
+ # build_name(:foo, :bar) # => "foo[bar]"
98
+ # build_name(:foo => {:bar => :baz}) # => "foo[bar][baz]"
99
+ # build_name(:foo, :bar => :baz) # => "foo[bar][baz]"
100
+ #
101
+ # @param [Array, Hash] args A hierarchy of strings.
102
+ #
103
+ # @return [String] The expected name of the form input.
104
+ def build_name(*args)
105
+ args.extend(DeepFlattening)
106
+ args = args.deep_flatten
107
+ name = args.shift.to_s
108
+ name + args.map {|piece| "[#{piece}]"}.join
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,20 @@
1
+ module RSpec::TagMatchers
2
+ # Matches +<select>+ tags in HTML.
3
+ #
4
+ # @example Matching a select tag for a user's role
5
+ # it { should have_select.for(:user => :role) }
6
+ #
7
+ # @return [HasSelect]
8
+ def have_select
9
+ HasSelect.new
10
+ end
11
+
12
+ # A matcher that matches +<select>+ tags.
13
+ class HasSelect < HasInput
14
+
15
+ # Initializes a HasSelect matcher that matches +select+ elements.
16
+ def initialize
17
+ super(:select)
18
+ end
19
+ end
20
+ end