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 +1 -0
- data/.yardopts +1 -1
- data/{docs/CHANGELOG.md → CHANGELOG.md} +10 -3
- data/Gemfile +0 -2
- data/README.md +47 -24
- data/Rakefile +7 -0
- data/assets/form.html +139 -0
- data/assets/ordered_list.html +9 -0
- data/assets/paragraphs.html +3 -0
- data/assets/quotes.html +6 -0
- data/assets/search_and_submit.html +9 -0
- data/assets/special.html +22 -0
- data/lib/rspec2-rails-views-matchers.rb +469 -1
- data/{docs/mikhalok.jpg → mikhalok.jpg} +0 -0
- data/rspec2-rails-views-matchers.gemspec +6 -5
- data/spec/matchers/form_matchers_spec.rb +214 -213
- data/spec/matchers/have_tag_spec.rb +137 -166
- data/spec/spec_helper.rb +0 -11
- metadata +34 -31
- data/lib/rspec/rails/views/matchers.rb +0 -1
- data/lib/rspec/rails/views/matchers/have_tag.rb +0 -325
- data/lib/version.rb +0 -13
data/.gitignore
CHANGED
data/.yardopts
CHANGED
@@ -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
data/README.md
CHANGED
@@ -1,9 +1,7 @@
|
|
1
|
-
Test views, be true! :)
|
1
|
+
Test views, be true! :) [](http://travis-ci.org/kucaahbe/rspec2-rails-views-matchers)
|
2
2
|
=======================
|
3
3
|
|
4
|
-
[](http://www.myspace.com/lyapis "Lyapis Trubetskoy ska-punk band")
|
4
|
+
[](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
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
- [
|
43
|
-
- [
|
44
|
-
- [
|
45
|
-
- [
|
46
|
-
- [
|
47
|
-
- [
|
48
|
-
- [
|
49
|
-
- [
|
50
|
-
- [
|
51
|
-
- [
|
52
|
-
- [
|
53
|
-
|
54
|
-
|
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:
|
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>
|
data/assets/quotes.html
ADDED
data/assets/special.html
ADDED
@@ -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 '
|
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
|