rspec2-rails-views-matchers 0.1.6 → 0.2.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 CHANGED
@@ -1,6 +1,7 @@
1
1
  pkg/*
2
2
  *.gem
3
3
  .bundle
4
+ .rvmrc
4
5
  Gemfile.lock
5
6
  # documentation:
6
7
  html
data/.yardopts CHANGED
@@ -1,4 +1,4 @@
1
1
  --no-private
2
2
  --readme README.md
3
- --files docs/CHANGELOG.md
3
+ --files CHANGELOG.md
4
4
  --markup rdoc
@@ -4,15 +4,22 @@ Changelog
4
4
  unreleased(TODO)
5
5
  ----------------
6
6
 
7
- * add message for should\_not
8
7
  * add description
9
- * raise exception when wrong parametres specified(:count and :minimum simultaneously)
10
- * organize code
11
8
  * add :without to have\_tag?
12
9
  * ?make possible constructions like:
13
10
 
14
11
  rendered.should have(3).tags('div').with(:class => 'some-class').and_content(/some content/)
15
12
 
13
+
14
+ 0.2.0
15
+ -----
16
+
17
+ * a little bit refactoring
18
+ * added some html5 inputs
19
+ * added message for should\_not
20
+ * raise exception when wrong parametres specified(:count and :minimum (or :maximum) simultaneously)
21
+ * support all versions of nokogiri since 1.4.4
22
+
16
23
  0.1.6 (nokogiri update)
17
24
  -----------------------
18
25
 
data/Gemfile CHANGED
@@ -1,4 +1,2 @@
1
1
  source "http://rubygems.org"
2
-
3
- # Specify your gem's dependencies in rspec2-rails-views-matchers.gemspec
4
2
  gemspec
data/README.md CHANGED
@@ -1,9 +1,7 @@
1
- Test views, be true! :)
1
+ Test views, be true! :) [![Build Status](http://travis-ci.org/kucaahbe/rspec2-rails-views-matchers.png)](http://travis-ci.org/kucaahbe/rspec2-rails-views-matchers)
2
2
  =======================
3
3
 
4
- [![Build Status](http://travis-ci.org/kucaahbe/rspec2-rails-views-matchers.png)](http://travis-ci.org/kucaahbe/rspec2-rails-views-matchers)
5
-
6
- [![Mikhalok](http://investigator.org.ua/wp-content/uploads/01_500_liapis_powe-300x192.jpg)](http://www.myspace.com/lyapis "Lyapis Trubetskoy ska-punk band")
4
+ [![Mikhalok](https://github.com/kucaahbe/rspec2-rails-views-matchers/raw/master/mikhalok.jpg)](http://www.myspace.com/lyapis "Lyapis Trubetskoy ska-punk band")
7
5
 
8
6
  Why?
9
7
  ===
@@ -24,41 +22,60 @@ add to your Gemfile(in group :test :) ):
24
22
 
25
23
  gem 'rspec2-rails-views-matchers'
26
24
 
25
+ Instructions for [installing nokogiri here.](http://nokogiri.org/tutorials/installing_nokogiri.html)
26
+
27
27
  Usage
28
28
  -----
29
29
 
30
- some examples:
31
-
32
- rendered.should have_tag('form',:with => {:action => user_path, :method => 'post'}) do
30
+ simple example:
31
+
32
+ view=<<-HTML
33
+ <h1>Simple Form</h1>
34
+
35
+ <form action="/users" method="post">
36
+ <p>
37
+ <input type="email" name="user[email]" />
38
+ </p>
39
+ <p>
40
+ <input type="submit" id="special_submit" />
41
+ </p>
42
+ </form>
43
+ HTML
44
+ view.should have_tag('form',:with => {:action => '/users', :method => 'post'}) do
33
45
  with_tag "input", :with => { :name => "user[email]", :type => 'email' }
34
46
  with_tag "input#special_submit", :count => 1
35
47
  without_tag "h1", :text => 'unneeded tag'
36
48
  without_tag "p", :text => /content/i
37
49
  end
38
50
 
39
- additional list of defined matchers ("form" matchers)
40
- -----------------------------------------------------
41
-
42
- - [have_form](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:have_form)
43
- - [with_checkbox](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:with_checkbox)
44
- - [with_file_field](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:with_file_field)
45
- - [with_hidden_field](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:with_hidden_field)
46
- - [with_option](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:with_option)
47
- - [with_password_field](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:with_password_field)
48
- - [with_radio_button](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:with_radio_button)
49
- - [with_select](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:with_select)
50
- - [with_submit](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:with_submit)
51
- - [with_text_area](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:with_text_area)
52
- - [with_text_field](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:with_text_field)
53
-
54
- and of course you can use <strong>without_</strong>matchers
51
+ Also included special matchers for form inputs:
52
+ -----------------------------------------------
53
+
54
+ - [have\_form](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:have_form)
55
+ - [with\_checkbox](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:with_checkbox)
56
+ - [with\_email\_field](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:with_email_field)
57
+ - [with\_file\_field](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:with_file_field)
58
+ - [with\_hidden\_field](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:with_hidden_field)
59
+ - [with\_option](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:with_option)
60
+ - [with\_password_field](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:with_password_field)
61
+ - [with\_radio\_button](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:with_radio_button)
62
+ - [with\_select](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:with_select)
63
+ - [with\_submit](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:with_submit)
64
+ - [with\_text\_area](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:with_text_area)
65
+ - [with\_text\_field](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:with_text_field)
66
+ - [with\_url\_field](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:with_url_field)
67
+ - [with\_number\_field](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:with_number_field)
68
+ - [with\_range\_field](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:with_range_field)
69
+ - [with\_date\_field](http://rdoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers:with_date_field)
70
+
71
+ and of course you can use <strong>without_</strong>matchers(see documentation).
55
72
 
56
73
  More info
57
74
  ---------
58
75
 
59
76
  You can find [on RubyDoc](http://rubydoc.info/github/kucaahbe/rspec2-rails-views-matchers/master/RSpec/Matchers), take a look at {RSpec::Matchers#have\_tag have\_tag} method.
60
77
 
61
- Also, please read {file:docs/CHANGELOG.md CHANGELOG}, it might be helpful.
78
+ Also, please read {file:CHANGELOG.md CHANGELOG}, it might be helpful.
62
79
 
63
80
  Contribution
64
81
  ============
@@ -73,3 +90,9 @@ Contributors
73
90
 
74
91
  - [Kelly Felkins](http://github.com/kellyfelkins)
75
92
  - [Ryan Wilcox](http://github.com/rwilcox)
93
+ - [Simon Schoeters](https://github.com/cimm)
94
+
95
+ License
96
+ =======
97
+
98
+ This gem is released under the MIT license.
data/Rakefile CHANGED
@@ -2,12 +2,19 @@ require 'bundler'
2
2
  require 'rspec/core/rake_task'
3
3
  Bundler::GemHelper.install_tasks
4
4
 
5
+ gemspec = eval(File.read(Dir["*.gemspec"].first))
6
+
5
7
  task :default => :spec
6
8
 
7
9
  RSpec::Core::RakeTask.new(:spec) do |t|
8
10
  t.rspec_opts='--tag ~wip'
9
11
  end
10
12
 
13
+ desc "Validate the gemspec"
14
+ task :gemspec do
15
+ gemspec.validate && puts('gemspec valid')
16
+ end
17
+
11
18
  namespace :spec do
12
19
  RSpec::Core::RakeTask.new(:wip) do |t|
13
20
  t.rspec_opts='--tag wip'
data/assets/form.html ADDED
@@ -0,0 +1,139 @@
1
+ <form action="/books" class="formtastic book" id="new_book" method="post">
2
+ <div style="margin:0;padding:0;display:inline">
3
+ <input name="authenticity_token" type="hidden" value="718WaH76RAbIVhDlnOidgew62ikn8IUCOyWLEqjw1GE=" />
4
+ </div>
5
+
6
+ <fieldset class="inputs">
7
+ <ol>
8
+ <li class="select required" id="book_publisher_input">
9
+ <label for="book_publisher_id">
10
+ Publisher<abbr title="required">*</abbr>
11
+ </label>
12
+ <select id="book_publisher_id" name="book[publisher_id]">
13
+ <option value=""></option>
14
+ <option value="1" selected="selected">The Pragmatic Bookshelf</option>
15
+ <option value="2">sitepoint</option>
16
+ <option value="3">O'Reilly</option>
17
+ </select>
18
+ </li>
19
+
20
+ <li class="string required" id="book_title_input">
21
+ <label for="book_title">
22
+ Title<abbr title="required">*</abbr>
23
+ </label>
24
+ <input id="book_title" maxlength="255" name="book[title]" size="50" type="text" />
25
+ </li>
26
+
27
+ <li class="string required" id="book_author_input">
28
+ <label for="book_author">
29
+ Author<abbr title="required">*</abbr>
30
+ </label>
31
+ <input id="book_author" maxlength="255" name="book[author]" size="50" type="text" value="Authorname" />
32
+ </li>
33
+
34
+ <li class="email optional" id="user_email_input">
35
+ <label for="user_email">
36
+ E-mail address:
37
+ </label>
38
+ <input id="user_email" name="user[email]" size="30" type="email" value="email@example.com" />
39
+ </li>
40
+
41
+ <li class="email_confirmation optional" id="user_email_confirmation_input">
42
+ <label for="user_email_confirmation">
43
+ E-mail address confirmation:
44
+ </label>
45
+ <input id="user_email_confirmation" name="user[email_confirmation]" size="30" type="email" />
46
+ </li>
47
+
48
+ <li class="url optional" id="user_url_input">
49
+ <label for="user_url">
50
+ E-mail address:
51
+ </label>
52
+ <input id="user_url" name="user[url]" size="30" type="url" value="http://user.com" />
53
+ </li>
54
+
55
+ <li class="password optional" id="user_password_input">
56
+ <label for="user_password">
57
+ Password:
58
+ </label>
59
+ <input id="user_password" name="user[password]" size="30" type="password" />
60
+ </li>
61
+
62
+ <li class="file optional" id="form_file_input">
63
+ <label for="form_file">
64
+ File
65
+ </label>
66
+ <input id="form_file" name="form[file]" type="file" />
67
+ </li>
68
+
69
+ <li class="text required" id="book_description_input">
70
+ <label for="book_description">
71
+ Description<abbr title="required">*</abbr>
72
+ </label>
73
+ <textarea cols="40" id="book_description" name="book[description]" rows="20"></textarea>
74
+ </li>
75
+
76
+ <li class="boolean required" id="book_still_in_print_input">
77
+ <label for="book_still_in_print">
78
+ Still in print<abbr title="required">*</abbr>
79
+ </label>
80
+ <input name="book[still_in_print]" type="hidden" value="0" />
81
+ <input id="book_still_in_print" name="book[still_in_print]" type="checkbox" value="1" />
82
+ </li>
83
+ <li class="number required">
84
+ <label for="book_number">
85
+ Still in print<abbr title="required">*</abbr>
86
+ </label>
87
+ <input name="number" type="number" />
88
+ <input name="number_defined" type="number" value="3" />
89
+ </li>
90
+
91
+ <li class="range required">
92
+ <label for="range">
93
+ Still in print<abbr title="required">*</abbr>
94
+ </label>
95
+ <input name="range1" type="range" min="1" max="3" />
96
+ <input name="range2" type="range" min="1" max="3" value="2" />
97
+ </li>
98
+
99
+ <li class="date required">
100
+ <label for="date">
101
+ Something<abbr title="required">*</abbr>
102
+ </label>
103
+ <input name="book_date" type="date" />
104
+ <input name="book_month" type="month" value="5" />
105
+ <input name="book_week" type="week" />
106
+ <input name="book_time" type="time" />
107
+ <input name="book_datetime" type="datetime" />
108
+ <input name="book_datetime_local" type="datetime-local" />
109
+ </li>
110
+
111
+ <li class="radio required" id="form_name_input">
112
+ <fieldset>
113
+ <legend class="label">
114
+ <label>Name<abbr title="required">*</abbr></label>
115
+ </legend>
116
+ <ol>
117
+ <li class="name_true">
118
+ <label for="form_name_true">
119
+ <input id="form_name_true" name="form[name]" type="radio" value="true" /> Yes
120
+ </label>
121
+ </li>
122
+ <li class="name_false">
123
+ <label for="form_name_false">
124
+ <input id="form_name_false" name="form[name]" type="radio" value="false" /> No</label>
125
+ </li>
126
+ </ol>
127
+ </fieldset>
128
+ </li>
129
+ </ol>
130
+ </fieldset>
131
+
132
+ <fieldset class="buttons">
133
+ <ol>
134
+ <li class="commit">
135
+ <input class="create" id="book_submit" name="commit" type="submit" value="Create Book" />
136
+ </li>
137
+ </ol>
138
+ </fieldset>
139
+ </form>
@@ -0,0 +1,9 @@
1
+ <html>
2
+ <body>
3
+ <ol class="menu">
4
+ <li>list item 1</li>
5
+ <li>list item 2</li>
6
+ <li>list item 3</li>
7
+ </ol>
8
+ </body>
9
+ </html>
@@ -0,0 +1,3 @@
1
+ <p>tag one</p>
2
+ <p>tag two</p>
3
+ <p>tag three</p>
@@ -0,0 +1,6 @@
1
+ <div>sample text</div>
2
+ <span>sample with 'single' quotes</span>
3
+ <span>sample with 'single' and "double" quotes</span>
4
+ <p>one</p>
5
+ <p>two</p>
6
+ <p>three </p>
@@ -0,0 +1,9 @@
1
+ <div class="class-one class-two">
2
+ some content
3
+ <div id="div">some other div</div>
4
+ <p class="paragraph">la<strong>lala</strong></p>
5
+ </div>
6
+ <form id="some_form">
7
+ <input id="search" type="text" />
8
+ <input type="submit" value="Save" />
9
+ </form>
@@ -0,0 +1,22 @@
1
+ <table>
2
+ <tr>
3
+ <td>user_1</td>
4
+ <td id="other-special">user_2</td>
5
+ <td>user_3</td>
6
+ </tr>
7
+ <tr>
8
+ <td>a</td>
9
+ <td id="special">a</td>
10
+ <td>a</td>
11
+ </tr>
12
+ </table>
13
+
14
+ <div class="one">text</div>
15
+ <div class="one">text</div>
16
+ <div class="one">text</div>
17
+ <div class="one">text bla</div>
18
+ <div class="one">content bla</div>
19
+ <div class="one">content</div>
20
+ <div class="two">content bla</div>
21
+ <div class="two">content</div>
22
+ <div class="two">text</div>
@@ -1 +1,469 @@
1
- require 'rspec/rails/views/matchers'
1
+ require 'nokogiri'
2
+
3
+ module RSpec
4
+ module Matchers
5
+
6
+ # @api
7
+ # @private
8
+ class NokogiriMatcher
9
+ attr_reader :failure_message, :negative_failure_message
10
+ attr_reader :parent_scope, :current_scope
11
+
12
+ TAG_NOT_FOUND_MSG = %Q|expected following:\n%s\nto have at least 1 element matching "%s", found 0.|
13
+ TAG_FOUND_MSG = %Q|expected following:\n%s\nto NOT have element matching "%s", found %s.|
14
+ WRONG_COUNT_MSG = %Q|expected following:\n%s\nto have %s element(s) matching "%s", found %s.|
15
+ RIGHT_COUNT_MSG = %Q|expected following:\n%s\nto NOT have %s element(s) matching "%s", but found.|
16
+ BETWEEN_COUNT_MSG = %Q|expected following:\n%s\nto have at least %s and at most %s element(s) matching "%s", found %s.|
17
+ RIGHT_BETWEEN_COUNT_MSG = %Q|expected following:\n%s\nto NOT have at least %s and at most %s element(s) matching "%s", but found %s.|
18
+ AT_MOST_MSG = %Q|expected following:\n%s\nto have at most %s element(s) matching "%s", found %s.|
19
+ RIGHT_AT_MOST_MSG = %Q|expected following:\n%s\nto NOT have at most %s element(s) matching "%s", but found %s.|
20
+ AT_LEAST_MSG = %Q|expected following:\n%s\nto have at least %s element(s) matching "%s", found %s.|
21
+ RIGHT_AT_LEAST_MSG = %Q|expected following:\n%s\nto NOT have at least %s element(s) matching "%s", but found %s.|
22
+ REGEXP_NOT_FOUND_MSG = %Q|%s regexp expected within "%s" in following template:\n%s|
23
+ REGEXP_FOUND_MSG = %Q|%s regexp unexpected within "%s" in following template:\n%s\nbut was found.|
24
+ TEXT_NOT_FOUND_MSG = %Q|"%s" expected within "%s" in following template:\n%s|
25
+ TEXT_FOUND_MSG = %Q|"%s" unexpected within "%s" in following template:\n%s\nbut was found.|
26
+ WRONG_COUNT_ERROR_MSG = %Q|:count with :minimum or :maximum has no sence!|
27
+ MIN_MAX_ERROR_MSG = %Q|:minimum shold be less than :maximum!|
28
+ BAD_RANGE_ERROR_MSG = %Q|Your :count range(%s) has no sence!|
29
+
30
+ def initialize tag, options={}, &block
31
+ @tag, @options, @block = tag.to_s, options, block
32
+
33
+ if attrs = @options.delete(:with)
34
+ if classes=attrs.delete(:class)
35
+ classes = case classes
36
+ when Array
37
+ classes.join('.')
38
+ when String
39
+ classes.gsub("\s",'.')
40
+ end
41
+ @tag << '.'+classes
42
+ end
43
+ html_attrs_string=''
44
+ attrs.each_pair { |k,v| html_attrs_string << %Q{[#{k.to_s}='#{v.to_s}']} }
45
+ @tag << html_attrs_string
46
+ end
47
+
48
+ validate_options!
49
+ end
50
+
51
+ def matches? document, &block
52
+ @block = block if block
53
+
54
+ case document
55
+ when String
56
+ @parent_scope = @current_scope = Nokogiri::HTML(document).css(@tag)
57
+ @document = document
58
+ else
59
+ @parent_scope = document.current_scope
60
+ @current_scope = document.parent_scope.css(@tag)
61
+ @document = @parent_scope.to_html
62
+ end
63
+
64
+ if tag_presents? and content_right? and count_right?
65
+ @current_scope = @parent_scope
66
+ @block.call if @block
67
+ true
68
+ else
69
+ false
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def tag_presents?
76
+ if @current_scope.first
77
+ @count = @current_scope.count
78
+ @negative_failure_message = TAG_FOUND_MSG % [@document, @tag, @count]
79
+ true
80
+ else
81
+ @failure_message = TAG_NOT_FOUND_MSG % [@document, @tag]
82
+ false
83
+ end
84
+ end
85
+
86
+ def count_right?
87
+ case @options[:count]
88
+ when Integer
89
+ ((@negative_failure_message=RIGHT_COUNT_MSG % [@document,@count,@tag]) && @count == @options[:count]) || (@failure_message=WRONG_COUNT_MSG % [@document,@options[:count],@tag,@count]; false)
90
+ when Range
91
+ ((@negative_failure_message=RIGHT_BETWEEN_COUNT_MSG % [@document,@options[:count].min,@options[:count].max,@tag,@count]) && @options[:count].member?(@count)) || (@failure_message=BETWEEN_COUNT_MSG % [@document,@options[:count].min,@options[:count].max,@tag,@count]; false)
92
+ when nil
93
+ if @options[:maximum]
94
+ ((@negative_failure_message=RIGHT_AT_MOST_MSG % [@document,@options[:maximum],@tag,@count]) && @count <= @options[:maximum]) || (@failure_message=AT_MOST_MSG % [@document,@options[:maximum],@tag,@count]; false)
95
+ elsif @options[:minimum]
96
+ ((@negative_failure_message=RIGHT_AT_LEAST_MSG % [@document,@options[:minimum],@tag,@count]) && @count >= @options[:minimum]) || (@failure_message=AT_LEAST_MSG % [@document,@options[:minimum],@tag,@count]; false)
97
+ else
98
+ true
99
+ end
100
+ end
101
+ end
102
+
103
+ def content_right?
104
+ return true unless @options[:text]
105
+
106
+ case text=@options[:text]
107
+ when Regexp
108
+ new_scope = @current_scope.css(":regexp('#{text}')",Class.new {
109
+ def regexp node_set, text
110
+ node_set.find_all { |node| node.content =~ Regexp.new(text) }
111
+ end
112
+ }.new)
113
+ unless new_scope.empty?
114
+ @count = new_scope.count
115
+ @negative_failure_message = REGEXP_FOUND_MSG % [text.inspect,@tag,@document]
116
+ true
117
+ else
118
+ @failure_message=REGEXP_NOT_FOUND_MSG % [text.inspect,@tag,@document]
119
+ false
120
+ end
121
+ else
122
+ css_param = text.gsub(/'/) { %q{\000027} }
123
+ new_scope = @current_scope.css(":content('#{css_param}')",Class.new {
124
+ def content node_set, text
125
+ match_text = text.gsub(/\\000027/, "'")
126
+ node_set.find_all { |node| node.content == match_text }
127
+ end
128
+ }.new)
129
+ unless new_scope.empty?
130
+ @count = new_scope.count
131
+ @negative_failure_message = TEXT_FOUND_MSG % [text,@tag,@document]
132
+ true
133
+ else
134
+ @failure_message=TEXT_NOT_FOUND_MSG % [text,@tag,@document]
135
+ false
136
+ end
137
+ end
138
+ end
139
+
140
+ protected
141
+
142
+ def validate_options!
143
+ raise 'wrong :count specified' unless [Range, NilClass].include?(@options[:count].class) or @options[:count].is_a?(Integer)
144
+
145
+ [:min, :minimum, :max, :maximum].each do |key|
146
+ raise WRONG_COUNT_ERROR_MSG if @options.has_key?(key) and @options.has_key?(:count)
147
+ end
148
+
149
+ begin
150
+ raise MIN_MAX_ERROR_MSG if @options[:minimum] > @options[:maximum]
151
+ rescue NoMethodError # nil > 4
152
+ rescue ArgumentError # 2 < nil
153
+ end
154
+
155
+ begin
156
+ begin
157
+ raise BAD_RANGE_ERROR_MSG % [@options[:count].to_s] if @options[:count] && @options[:count].is_a?(Range) && (@options[:count].min.nil? or @options[:count].min < 0)
158
+ rescue ArgumentError, "comparison of String with" # if @options[:count] == 'a'..'z'
159
+ raise BAD_RANGE_ERROR_MSG % [@options[:count].to_s]
160
+ end
161
+ rescue TypeError # fix for 1.8.7 for 'rescue ArgumentError, "comparison of String with"' stroke
162
+ raise BAD_RANGE_ERROR_MSG % [@options[:count].to_s]
163
+ end
164
+
165
+ @options[:minimum] ||= @options.delete(:min)
166
+ @options[:maximum] ||= @options.delete(:max)
167
+ end
168
+
169
+ end
170
+
171
+ # have_tag matcher
172
+ #
173
+ # @yield block where you should put with_tag
174
+ #
175
+ # @param [String] tag css selector for tag you want to match
176
+ # @param [Hash] options options hash(see below)
177
+ # @option options [Hash] :with hash with other attributes, within this, key :class have special meaning, you may specify it as array of expected classes or string of classes separated by spaces, order does not matter
178
+ # @option options [Fixnum] :count count of matched tags(DO NOT USE :count with :minimum(:min) or :maximum(:max)*)
179
+ # @option options [Range] :count count of matched tags should be between range minimum and maximum values
180
+ # @option options [Fixnum] :minimum minimum count of elements to match
181
+ # @option options [Fixnum] :min same as :minimum
182
+ # @option options [Fixnum] :maximum maximum count of elements to match
183
+ # @option options [Fixnum] :max same as :maximum
184
+ #
185
+ #
186
+ # @example
187
+ # rendered.should have_tag('div')
188
+ # rendered.should have_tag('h1.header')
189
+ # rendered.should have_tag('div#footer')
190
+ # rendered.should have_tag('input#email', :with => { :name => 'user[email]', :type => 'email' } )
191
+ # rendered.should have_tag('div', :count => 3) # matches exactly 3 'div' tags
192
+ # rendered.should have_tag('div', :count => 3..7) # something like have_tag('div', :minimum => 3, :maximum => 7)
193
+ # rendered.should have_tag('div', :minimum => 3) # matches more(or equal) than 3 'div' tags
194
+ # rendered.should have_tag('div', :maximum => 3) # matches less(or equal) than 3 'div' tags
195
+ # rendered.should have_tag('p', :text => 'some content') # will match "<p>some content</p>"
196
+ # rendered.should have_tag('p', :text => /some content/i) # will match "<p>sOme cOntEnt</p>"
197
+ # rendered.should have_tag('textarea', :with => {:name => 'user[description]'}, :text => "I like pie")
198
+ # "<html>
199
+ # <body>
200
+ # <h1>some html document</h1>
201
+ # </body>
202
+ # </html>".should have_tag('body') { with_tag('h1', :text => 'some html document') }
203
+ # '<div class="one two">'.should have_tag('div', :with => { :class => ['two', 'one'] })
204
+ # '<div class="one two">'.should have_tag('div', :with => { :class => 'two one' })
205
+ def have_tag tag, options={}, &block
206
+ @__current_scope_for_nokogiri_matcher = NokogiriMatcher.new(tag, options, &block)
207
+ end
208
+
209
+ # with_tag matcher
210
+ # @yield
211
+ # @see #have_tag
212
+ # @note this should be used within block of have_tag matcher
213
+ def with_tag tag, options={}, &block
214
+ @__current_scope_for_nokogiri_matcher.should have_tag(tag, options, &block)
215
+ end
216
+
217
+ # without_tag matcher
218
+ # @yield
219
+ # @see #have_tag
220
+ # @note this should be used within block of have_tag matcher
221
+ def without_tag tag, options={}, &block
222
+ @__current_scope_for_nokogiri_matcher.should_not have_tag(tag, options, &block)
223
+ end
224
+
225
+ def have_form action_url, method, options={}, &block
226
+ options[:with] ||= {}
227
+ id = options[:with].delete(:id)
228
+ tag = 'form'; tag += '#'+id if id
229
+ options[:with].merge!(:action => action_url)
230
+ options[:with].merge!(:method => method.to_s)
231
+ have_tag tag, options, &block
232
+ end
233
+
234
+ def with_hidden_field name, value=nil
235
+ options = { :with => { :name => name, :type => 'hidden' } }
236
+ options[:with].merge!(:value => value) if value
237
+ should_have_input(options)
238
+ end
239
+
240
+ def without_hidden_field name, value=nil
241
+ options = { :with => { :name => name, :type => 'hidden' } }
242
+ options[:with].merge!(:value => value) if value
243
+ should_not_have_input(options)
244
+ end
245
+
246
+ def with_text_field name, value=nil
247
+ options = { :with => { :name => name, :type => 'text' } }
248
+ options[:with].merge!(:value => value) if value
249
+ should_have_input(options)
250
+ end
251
+
252
+ def without_text_field name, value=nil
253
+ options = { :with => { :name => name, :type => 'text' } }
254
+ options[:with].merge!(:value => value) if value
255
+ should_not_have_input(options)
256
+ end
257
+
258
+ def with_email_field name, value=nil
259
+ options = { :with => { :name => name, :type => 'email' } }
260
+ options[:with].merge!(:value => value) if value
261
+ should_have_input(options)
262
+ end
263
+
264
+ def without_email_field name, value=nil
265
+ options = { :with => { :name => name, :type => 'email' } }
266
+ options[:with].merge!(:value => value) if value
267
+ should_not_have_input(options)
268
+ end
269
+
270
+ def with_url_field name, value=nil
271
+ options = { :with => { :name => name, :type => 'url' } }
272
+ options[:with].merge!(:value => value) if value
273
+ should_have_input(options)
274
+ end
275
+
276
+ def without_url_field name, value=nil
277
+ options = { :with => { :name => name, :type => 'url' } }
278
+ options[:with].merge!(:value => value) if value
279
+ should_not_have_input(options)
280
+ end
281
+
282
+ def with_number_field name, value=nil
283
+ options = { :with => { :name => name, :type => 'number' } }
284
+ options[:with].merge!(:value => value.to_s) if value
285
+ should_have_input(options)
286
+ end
287
+
288
+ def without_number_field name, value=nil
289
+ options = { :with => { :name => name, :type => 'number' } }
290
+ options[:with].merge!(:value => value.to_s) if value
291
+ should_not_have_input(options)
292
+ end
293
+
294
+ def with_range_field name, min, max, options={}
295
+ options = { :with => { :name => name, :type => 'range', :min => min.to_s, :max => max.to_s }.merge(options.delete(:with)||{}) }
296
+ should_have_input(options)
297
+ end
298
+
299
+ def without_range_field name, min=nil, max=nil, options={}
300
+ options = { :with => { :name => name, :type => 'range' }.merge(options.delete(:with)||{}) }
301
+ options[:with].merge!(:min => min.to_s) if min
302
+ options[:with].merge!(:max => max.to_s) if max
303
+ should_not_have_input(options)
304
+ end
305
+
306
+ DATE_FIELD_TYPES = %w( date month week time datetime datetime-local )
307
+
308
+ def with_date_field date_field_type, name=nil, options={}
309
+ date_field_type = date_field_type.to_s
310
+ raise "unknown type `#{date_field_type}` for date picker" unless DATE_FIELD_TYPES.include?(date_field_type)
311
+ options = { :with => { :type => date_field_type.to_s }.merge(options.delete(:with)||{}) }
312
+ options[:with].merge!(:name => name.to_s) if name
313
+ should_have_input(options)
314
+ end
315
+
316
+ def without_date_field date_field_type, name=nil, options={}
317
+ date_field_type = date_field_type.to_s
318
+ raise "unknown type `#{date_field_type}` for date picker" unless DATE_FIELD_TYPES.include?(date_field_type)
319
+ options = { :with => { :type => date_field_type.to_s }.merge(options.delete(:with)||{}) }
320
+ options[:with].merge!(:name => name.to_s) if name
321
+ should_not_have_input(options)
322
+ end
323
+
324
+ def with_password_field name
325
+ options = { :with => { :name => name, :type => 'password' } }
326
+ should_have_input(options)
327
+ end
328
+
329
+ def without_password_field name
330
+ options = { :with => { :name => name, :type => 'password' } }
331
+ should_not_have_input(options)
332
+ end
333
+
334
+ def with_file_field name
335
+ options = { :with => { :name => name, :type => 'file' } }
336
+ should_have_input(options)
337
+ end
338
+
339
+ def without_file_field name
340
+ options = { :with => { :name => name, :type => 'file' } }
341
+ should_not_have_input(options)
342
+ end
343
+
344
+ def with_text_area name
345
+ options = { :with => { :name => name } }
346
+ @__current_scope_for_nokogiri_matcher.should have_tag('textarea', options)
347
+ end
348
+
349
+ def without_text_area name
350
+ options = { :with => { :name => name } }
351
+ @__current_scope_for_nokogiri_matcher.should_not have_tag('textarea', options)
352
+ end
353
+
354
+ def with_checkbox name, value=nil
355
+ options = { :with => { :name => name, :type => 'checkbox' } }
356
+ options[:with].merge!(:value => value) if value
357
+ should_have_input(options)
358
+ end
359
+
360
+ def without_checkbox name, value=nil
361
+ options = { :with => { :name => name, :type => 'checkbox' } }
362
+ options[:with].merge!(:value => value) if value
363
+ should_not_have_input(options)
364
+ end
365
+
366
+ def with_radio_button name, value
367
+ options = { :with => { :name => name, :type => 'radio' } }
368
+ options[:with].merge!(:value => value)
369
+ should_have_input(options)
370
+ end
371
+
372
+ def without_radio_button name, value
373
+ options = { :with => { :name => name, :type => 'radio' } }
374
+ options[:with].merge!(:value => value)
375
+ should_not_have_input(options)
376
+ end
377
+
378
+ def with_select name, options={}, &block
379
+ options[:with] ||= {}
380
+ id = options[:with].delete(:id)
381
+ tag='select'; tag += '#'+id if id
382
+ options[:with].merge!(:name => name)
383
+ @__current_scope_for_nokogiri_matcher.should have_tag(tag, options, &block)
384
+ end
385
+
386
+ def without_select name, options={}, &block
387
+ options[:with] ||= {}
388
+ id = options[:with].delete(:id)
389
+ tag='select'; tag += '#'+id if id
390
+ options[:with].merge!(:name => name)
391
+ @__current_scope_for_nokogiri_matcher.should_not have_tag(tag, options, &block)
392
+ end
393
+
394
+ def with_option text, value=nil, options={}
395
+ options[:with] ||= {}
396
+ if value.is_a?(Hash)
397
+ options.merge!(value)
398
+ value=nil
399
+ end
400
+ tag='option'
401
+ options[:with].merge!(:value => value.to_s) if value
402
+ if options[:selected]
403
+ options[:with].merge!(:selected => "selected")
404
+ end
405
+ options.delete(:selected)
406
+ options.merge!(:text => text) if text
407
+ @__current_scope_for_nokogiri_matcher.should have_tag(tag, options)
408
+ end
409
+
410
+ def without_option text, value=nil, options={}
411
+ options[:with] ||= {}
412
+ if value.is_a?(Hash)
413
+ options.merge!(value)
414
+ value=nil
415
+ end
416
+ tag='option'
417
+ options[:with].merge!(:value => value.to_s) if value
418
+ if options[:selected]
419
+ options[:with].merge!(:selected => "selected")
420
+ end
421
+ options.delete(:selected)
422
+ options.merge!(:text => text) if text
423
+ @__current_scope_for_nokogiri_matcher.should_not have_tag(tag, options)
424
+ end
425
+
426
+ def with_button text, value=nil, options={}
427
+ options[:with] ||= {}
428
+ if value.is_a?(Hash)
429
+ options.merge!(value)
430
+ value=nil
431
+ end
432
+ options[:with].merge!(:value => value.to_s) if value
433
+ options.merge!(:text => text) if text
434
+ @__current_scope_for_nokogiri_matcher.should have_tag('button', options)
435
+ end
436
+
437
+ def without_button text, value=nil, options={}
438
+ options[:with] ||= {}
439
+ if value.is_a?(Hash)
440
+ options.merge!(value)
441
+ value=nil
442
+ end
443
+ options[:with].merge!(:value => value.to_s) if value
444
+ options.merge!(:text => text) if text
445
+ @__current_scope_for_nokogiri_matcher.should_not have_tag('button', options)
446
+ end
447
+
448
+ def with_submit value
449
+ options = { :with => { :type => 'submit', :value => value } }
450
+ should_have_input(options)
451
+ end
452
+
453
+ def without_submit value
454
+ options = { :with => { :type => 'submit', :value => value } }
455
+ should_not_have_input(options)
456
+ end
457
+
458
+ private
459
+
460
+ def should_have_input(options)
461
+ @__current_scope_for_nokogiri_matcher.should have_tag('input', options)
462
+ end
463
+
464
+ def should_not_have_input(options)
465
+ @__current_scope_for_nokogiri_matcher.should_not have_tag('input', options)
466
+ end
467
+
468
+ end
469
+ end