rspec2-rails-views-matchers 0.1.6 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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