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 +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! :) [![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
|
-
[![
|
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
|
-
|
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
|