webrat 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,28 @@
1
+ == 0.4.2 / 2009-02-24
2
+
3
+ * Major enhancements
4
+
5
+ * Significant improvements to have_selector. It now supports specifying
6
+ attributes in a hash and :count and :content options. See
7
+ have_selector_spec.rb for more.
8
+ * Add the same functionality mentioned above to have_xpath
9
+
10
+ * Minor enhancements
11
+
12
+ * Squeeze extra whitespace out of failures messages from contain
13
+ matcher
14
+ * Detect infinite redirects and raise a Webrat::InfiniteRedirectError
15
+ (Daniel Lucraft)
16
+
17
+ * Bug fixes
18
+
19
+ * Properly quote single and double quotes strings in XPath
20
+ * Fix warning caused by Nokogiri deprecating CSS::Parser.parse
21
+ (Aaron Patterson)
22
+ * Accept do/end blocks in matchers. [#157] (Peter Jaros)
23
+ * Quote --chdir option to mongrel_rails to support RAILS_ROOTs with spaces
24
+ (T.J. VanSlyke)
25
+
1
26
  == 0.4.1 / 2009-01-31
2
27
 
3
28
  * Minor enhancements
data/Rakefile CHANGED
@@ -28,7 +28,7 @@ spec = Gem::Specification.new do |s|
28
28
  s.extra_rdoc_files = %w(README.rdoc MIT-LICENSE.txt)
29
29
 
30
30
  # Dependencies
31
- s.add_dependency "nokogiri", ">= 1.1.0"
31
+ s.add_dependency "nokogiri", ">= 1.2.0"
32
32
 
33
33
  s.rubyforge_project = "webrat"
34
34
  end
@@ -51,6 +51,10 @@ module Webrat
51
51
  # Set the key that Selenium uses to determine the browser running. Default *firefox
52
52
  attr_accessor :selenium_browser_key
53
53
 
54
+ # How many redirects to the same URL should be halted as an infinite redirect
55
+ # loop? Defaults to 10
56
+ attr_accessor :infinite_redirect_limit
57
+
54
58
  def initialize # :nodoc:
55
59
  self.open_error_files = true
56
60
  self.parse_with_nokogiri = !Webrat.on_java?
@@ -58,6 +62,7 @@ module Webrat
58
62
  self.application_port = 3001
59
63
  self.application_address = 'localhost'
60
64
  self.selenium_server_port = 4444
65
+ self.infinite_redirect_limit = 10
61
66
  self.selenium_browser_key = '*firefox'
62
67
  end
63
68
 
@@ -26,15 +26,19 @@ module Webrat
26
26
  # ==== Returns
27
27
  # String:: The failure message.
28
28
  def failure_message
29
- "expected the following element's content to #{content_message}:\n#{@element}"
29
+ "expected the following element's content to #{content_message}:\n#{squeeze_space(@element)}"
30
30
  end
31
31
 
32
32
  # ==== Returns
33
33
  # String:: The failure message to be displayed in negative matches.
34
34
  def negative_failure_message
35
- "expected the following element's content to not #{content_message}:\n#{@element}"
35
+ "expected the following element's content to not #{content_message}:\n#{squeeze_space(@element)}"
36
36
  end
37
-
37
+
38
+ def squeeze_space(inner_text)
39
+ inner_text.gsub(/^\s*$/, "").squeeze("\n")
40
+ end
41
+
38
42
  def content_message
39
43
  case @content
40
44
  when String
@@ -1,24 +1,46 @@
1
+ require "webrat/core/matchers/have_xpath"
2
+
1
3
  module Webrat
2
4
  module Matchers
3
5
 
4
6
  class HaveSelector < HaveXpath #:nodoc:
5
-
6
7
  # ==== Returns
7
8
  # String:: The failure message.
8
9
  def failure_message
9
- "expected following text to match selector #{@expected}:\n#{@document}"
10
+ "expected following output to contain a #{tag_inspect} tag:\n#{@document}"
10
11
  end
11
-
12
+
12
13
  # ==== Returns
13
14
  # String:: The failure message to be displayed in negative matches.
14
15
  def negative_failure_message
15
- "expected following text to not match selector #{@expected}:\n#{@document}"
16
+ "expected following output to omit a #{tag_inspect}:\n#{@document}"
16
17
  end
17
-
18
+
19
+ def tag_inspect
20
+ options = @options.dup
21
+ count = options.delete(:count)
22
+ content = options.delete(:content)
23
+
24
+ html = "<#{@expected}"
25
+ options.each do |k,v|
26
+ html << " #{k}='#{v}'"
27
+ end
28
+
29
+ if content
30
+ html << ">#{content}</#{@expected}>"
31
+ else
32
+ html << "/>"
33
+ end
34
+
35
+ html
36
+ end
37
+
18
38
  def query
19
- Nokogiri::CSS::Parser.parse(*super).map { |ast| ast.to_xpath }
39
+ Nokogiri::CSS.parse(@expected.to_s).map do |ast|
40
+ ast.to_xpath
41
+ end.first
20
42
  end
21
-
43
+
22
44
  end
23
45
 
24
46
  # Matches HTML content against a CSS 3 selector.
@@ -28,24 +50,24 @@ module Webrat
28
50
  #
29
51
  # ==== Returns
30
52
  # HaveSelector:: A new have selector matcher.
31
- def have_selector(expected, &block)
32
- HaveSelector.new(expected, &block)
53
+ def have_selector(name, attributes = {}, &block)
54
+ HaveSelector.new(name, attributes, &block)
33
55
  end
34
56
  alias_method :match_selector, :have_selector
35
57
 
36
58
 
37
59
  # Asserts that the body of the response contains
38
60
  # the supplied selector
39
- def assert_have_selector(expected)
40
- hs = HaveSelector.new(expected)
41
- assert hs.matches?(response_body), hs.failure_message
61
+ def assert_have_selector(name, attributes = {}, &block)
62
+ matcher = HaveSelector.new(name, attributes, &block)
63
+ assert matcher.matches?(response_body), matcher.failure_message
42
64
  end
43
65
 
44
66
  # Asserts that the body of the response
45
67
  # does not contain the supplied string or regepx
46
- def assert_have_no_selector(expected)
47
- hs = HaveSelector.new(expected)
48
- assert !hs.matches?(response_body), hs.negative_failure_message
68
+ def assert_have_no_selector(name, attributes = {}, &block)
69
+ matcher = HaveSelector.new(name, attributes, &block)
70
+ assert !matcher.matches?(response_body), matcher.negative_failure_message
49
71
  end
50
72
 
51
73
  end
@@ -1,70 +1,20 @@
1
+ require "webrat/core/matchers/have_selector"
2
+
1
3
  module Webrat
2
-
3
4
  module HaveTagMatcher
4
5
 
5
- class HaveTag < ::Webrat::Matchers::HaveSelector #:nodoc:
6
- # ==== Returns
7
- # String:: The failure message.
8
- def failure_message
9
- "expected following output to contain a #{tag_inspect} tag:\n#{@document}"
10
- end
11
-
12
- # ==== Returns
13
- # String:: The failure message to be displayed in negative matches.
14
- def negative_failure_message
15
- "expected following output to omit a #{tag_inspect}:\n#{@document}"
16
- end
17
-
18
- def tag_inspect
19
- options = @expected.last.dup
20
- content = options.delete(:content)
21
-
22
- html = "<#{@expected.first}"
23
- options.each do |k,v|
24
- html << " #{k}='#{v}'"
25
- end
26
-
27
- if content
28
- html << ">#{content}</#{@expected.first}>"
29
- else
30
- html << "/>"
31
- end
32
-
33
- html
34
- end
35
-
36
- def query
37
- options = @expected.last.dup
38
- selector = @expected.first.to_s
39
-
40
- selector << ":contains('#{options.delete(:content)}')" if options[:content]
41
-
42
- options.each do |key, value|
43
- selector << "[#{key}='#{value}']"
44
- end
45
-
46
- Nokogiri::CSS::Parser.parse(selector).map { |ast| ast.to_xpath }
47
- end
48
- end
49
-
50
- def have_tag(name, attributes = {}, &block)
51
- HaveTag.new([name, attributes], &block)
6
+ def have_tag(*args, &block)
7
+ have_selector(*args, &block)
52
8
  end
53
9
 
54
10
  alias_method :match_tag, :have_tag
55
11
 
56
- # Asserts that the body of the response contains
57
- # the supplied tag with the associated selectors
58
- def assert_have_tag(name, attributes = {})
59
- ht = HaveTag.new([name, attributes])
60
- assert ht.matches?(response_body), ht.failure_message
12
+ def assert_have_tag(*args, &block)
13
+ assert_have_selector(*args, &block)
61
14
  end
62
15
 
63
- # Asserts that the body of the response
64
- # does not contain the supplied string or regepx
65
- def assert_have_no_tag(name, attributes = {})
66
- ht = HaveTag.new([name, attributes])
67
- assert !ht.matches?(response_body), ht.negative_failure_message
16
+ def assert_have_no_tag(*args, &block)
17
+ assert_have_no_selector(*args, &block)
68
18
  end
69
19
 
70
20
  end
@@ -5,53 +5,90 @@ module Webrat
5
5
  module Matchers
6
6
 
7
7
  class HaveXpath #:nodoc:
8
- def initialize(expected, &block)
8
+ def initialize(expected, options = {}, &block)
9
9
  @expected = expected
10
+ @options = options
10
11
  @block = block
11
12
  end
12
13
 
13
- def matches?(stringlike)
14
+ def matches?(stringlike, &block)
15
+ @block ||= block
16
+ matched = matches(stringlike)
17
+
18
+ if @options[:count]
19
+ matched.size == @options[:count] && (!@block || @block.call(matched))
20
+ else
21
+ matched.any? && (!@block || @block.call(matched))
22
+ end
23
+ end
24
+
25
+ def matches(stringlike)
14
26
  if Webrat.configuration.parse_with_nokogiri?
15
- matches_nokogiri?(stringlike)
27
+ nokogiri_matches(stringlike)
16
28
  else
17
- matches_rexml?(stringlike)
29
+ rexml_matches(stringlike)
18
30
  end
19
31
  end
20
32
 
21
- def matches_rexml?(stringlike)
33
+ def rexml_matches(stringlike)
22
34
  if REXML::Node === stringlike || Array === stringlike
23
35
  @query = query.map { |q| q.gsub(%r'//', './') }
24
36
  else
25
37
  @query = query
26
38
  end
27
39
 
40
+ add_options_conditions_to(@query)
41
+
28
42
  @document = Webrat.rexml_document(stringlike)
29
43
 
30
- matched = @query.map do |q|
44
+ @query.map do |q|
31
45
  if @document.is_a?(Array)
32
46
  @document.map { |d| REXML::XPath.match(d, q) }
33
47
  else
34
48
  REXML::XPath.match(@document, q)
35
49
  end
36
50
  end.flatten.compact
37
-
38
- matched.any? && (!@block || @block.call(matched))
39
51
  end
40
52
 
41
- def matches_nokogiri?(stringlike)
53
+ def nokogiri_matches(stringlike)
42
54
  if Nokogiri::XML::NodeSet === stringlike
43
- @query = query.map { |q| q.gsub(%r'//', './') }
55
+ @query = query.gsub(%r'//', './')
44
56
  else
45
57
  @query = query
46
58
  end
47
59
 
60
+ add_options_conditions_to(@query)
61
+
48
62
  @document = Webrat::XML.document(stringlike)
49
- matched = @document.xpath(*@query)
50
- matched.any? && (!@block || @block.call(matched))
63
+ @document.xpath(*@query)
64
+ end
65
+
66
+ def add_options_conditions_to(query)
67
+ add_attributes_conditions_to(query)
68
+ add_content_condition_to(query)
69
+ end
70
+
71
+ def add_attributes_conditions_to(query)
72
+ attribute_conditions = []
73
+
74
+ @options.each do |key, value|
75
+ next if [:content, :count].include?(key)
76
+ attribute_conditions << "@#{key} = #{xpath_escape(value)}"
77
+ end
78
+
79
+ if attribute_conditions.any?
80
+ query << "[#{attribute_conditions.join(' and ')}]"
81
+ end
82
+ end
83
+
84
+ def add_content_condition_to(query)
85
+ if @options[:content]
86
+ query << "[contains(., #{xpath_escape(@options[:content])})]"
87
+ end
51
88
  end
52
89
 
53
90
  def query
54
- [@expected].flatten.compact
91
+ @expected
55
92
  end
56
93
 
57
94
  # ==== Returns
@@ -64,7 +101,24 @@ module Webrat
64
101
  # String:: The failure message to be displayed in negative matches.
65
102
  def negative_failure_message
66
103
  "expected following text to not match xpath #{@expected}:\n#{@document}"
67
- end
104
+ end
105
+
106
+ protected
107
+
108
+ def xpath_escape(string)
109
+ if string.include?("'") && string.include?('"')
110
+ parts = string.split("'").map do |part|
111
+ "'#{part}'"
112
+ end
113
+
114
+ "concat(" + parts.join(", \"'\", ") + ")"
115
+ elsif string.include?("'")
116
+ "\"#{string}\""
117
+ else
118
+ "'#{string}'"
119
+ end
120
+ end
121
+
68
122
  end
69
123
 
70
124
  # Matches HTML content against an XPath query
@@ -74,18 +128,18 @@ module Webrat
74
128
  #
75
129
  # ==== Returns
76
130
  # HaveXpath:: A new have xpath matcher.
77
- def have_xpath(expected, &block)
78
- HaveXpath.new(expected, &block)
131
+ def have_xpath(expected, options = {}, &block)
132
+ HaveXpath.new(expected, options, &block)
79
133
  end
80
134
  alias_method :match_xpath, :have_xpath
81
135
 
82
- def assert_have_xpath(expected, &block)
83
- hs = HaveXpath.new(expected, &block)
136
+ def assert_have_xpath(expected, options = {}, &block)
137
+ hs = HaveXpath.new(expected, options, &block)
84
138
  assert hs.matches?(response_body), hs.failure_message
85
139
  end
86
140
 
87
- def assert_have_no_xpath(expected, &block)
88
- hs = HaveXpath.new(expected, &block)
141
+ def assert_have_no_xpath(expected, options = {}, &block)
142
+ hs = HaveXpath.new(expected, options, &block)
89
143
  assert !hs.matches?(response_body), hs.negative_failure_message
90
144
  end
91
145
 
@@ -9,6 +9,9 @@ module Webrat
9
9
  class PageLoadError < WebratError
10
10
  end
11
11
 
12
+ class InfiniteRedirectError < WebratError
13
+ end
14
+
12
15
  def self.session_class
13
16
  case Webrat.configuration.mode
14
17
  when :rails
@@ -112,11 +115,30 @@ For example:
112
115
  @http_method = http_method
113
116
  @data = data
114
117
 
115
- request_page(response_location, :get, {}) if internal_redirect?
118
+ if internal_redirect?
119
+ check_for_infinite_redirects
120
+ request_page(response_location, :get, {})
121
+ end
116
122
 
117
123
  return response
118
124
  end
125
+
126
+ def check_for_infinite_redirects
127
+ if current_url == response_location
128
+ @_identical_redirect_count ||= 0
129
+ @_identical_redirect_count += 1
130
+ end
131
+
132
+ if infinite_redirect_limit_exceeded?
133
+ raise InfiniteRedirectError.new("#{Webrat.configuration.infinite_redirect_limit} redirects to the same URL (#{current_url.inspect})")
134
+ end
135
+ end
119
136
 
137
+ def infinite_redirect_limit_exceeded?
138
+ Webrat.configuration.infinite_redirect_limit &&
139
+ (@_identical_redirect_count || 0) > Webrat.configuration.infinite_redirect_limit
140
+ end
141
+
120
142
  def success_code? #:nodoc:
121
143
  (200..499).include?(response_code)
122
144
  end
@@ -5,7 +5,7 @@ gem "extlib"
5
5
  require "extlib"
6
6
  require "merb-core"
7
7
 
8
- HashWithIndifferentAccess = Mash
8
+ # HashWithIndifferentAccess = Mash
9
9
 
10
10
  module Webrat
11
11
  class MerbSession < Session #:nodoc:
@@ -43,7 +43,7 @@ module Webrat
43
43
  selector << "[#{key}='#{value}']"
44
44
  end
45
45
 
46
- Nokogiri::CSS::Parser.parse(selector).map { |ast| ast.to_xpath }
46
+ Nokogiri::CSS.parse(selector).map { |ast| ast.to_xpath }
47
47
  end
48
48
  end
49
49
 
@@ -27,7 +27,7 @@ module Webrat
27
27
 
28
28
  def self.start_app_server #:nodoc:
29
29
  pid_file = prepare_pid_file("#{RAILS_ROOT}/tmp/pids", "mongrel_selenium.pid")
30
- system("mongrel_rails start -d --chdir=#{RAILS_ROOT} --port=#{Webrat.configuration.application_port} --environment=#{Webrat.configuration.application_environment} --pid #{pid_file} &")
30
+ system("mongrel_rails start -d --chdir='#{RAILS_ROOT}' --port=#{Webrat.configuration.application_port} --environment=#{Webrat.configuration.application_environment} --pid #{pid_file} &")
31
31
  TCPSocket.wait_for_service :host => Webrat.configuration.application_address, :port => Webrat.configuration.application_port.to_i
32
32
  end
33
33
 
data/lib/webrat.rb CHANGED
@@ -7,7 +7,7 @@ module Webrat
7
7
  class WebratError < StandardError
8
8
  end
9
9
 
10
- VERSION = '0.4.1'
10
+ VERSION = '0.4.2'
11
11
 
12
12
  def self.require_xml
13
13
  gem "nokogiri", ">= 1.0.6"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: webrat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryan Helmkamp
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-01-31 00:00:00 -05:00
12
+ date: 2009-02-24 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -20,7 +20,7 @@ dependencies:
20
20
  requirements:
21
21
  - - ">="
22
22
  - !ruby/object:Gem::Version
23
- version: 1.1.0
23
+ version: 1.2.0
24
24
  version:
25
25
  description: Webrat. Ruby Acceptance Testing for Web applications
26
26
  email: bryan@brynary.com
@@ -83,7 +83,6 @@ files:
83
83
  - lib/webrat/core_extensions/blank.rb
84
84
  - lib/webrat/core_extensions/deprecate.rb
85
85
  - lib/webrat/core_extensions/detect_mapped.rb
86
- - lib/webrat/core_extensions/hash_with_indifferent_access.rb
87
86
  - lib/webrat/core_extensions/meta_class.rb
88
87
  - lib/webrat/core_extensions/nil_to_param.rb
89
88
  - lib/webrat/mechanize.rb
@@ -1,131 +0,0 @@
1
- # This class has dubious semantics and we only have it so that
2
- # people can write params[:key] instead of params['key']
3
- # and they get the same value for both keys.
4
- class HashWithIndifferentAccess < Hash #:nodoc:
5
- def initialize(constructor = {})
6
- if constructor.is_a?(Hash)
7
- super()
8
- update(constructor)
9
- else
10
- super(constructor)
11
- end
12
- end
13
-
14
- def default(key = nil)
15
- if key.is_a?(Symbol) && include?(key = key.to_s)
16
- self[key]
17
- else
18
- super
19
- end
20
- end
21
-
22
- alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
23
- alias_method :regular_update, :update unless method_defined?(:regular_update)
24
-
25
- #
26
- # Assigns a new value to the hash.
27
- #
28
- # Example:
29
- #
30
- # hash = HashWithIndifferentAccess.new
31
- # hash[:key] = "value"
32
- #
33
- def []=(key, value)
34
- regular_writer(convert_key(key), convert_value(value))
35
- end
36
-
37
- #
38
- # Updates the instantized hash with values from the second.
39
- #
40
- # Example:
41
- #
42
- # >> hash_1 = HashWithIndifferentAccess.new
43
- # => {}
44
- #
45
- # >> hash_1[:key] = "value"
46
- # => "value"
47
- #
48
- # >> hash_2 = HashWithIndifferentAccess.new
49
- # => {}
50
- #
51
- # >> hash_2[:key] = "New Value!"
52
- # => "New Value!"
53
- #
54
- # >> hash_1.update(hash_2)
55
- # => {"key"=>"New Value!"}
56
- #
57
- def update(other_hash)
58
- other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
59
- self
60
- end
61
-
62
- alias_method :merge!, :update
63
-
64
- # Checks the hash for a key matching the argument passed in
65
- def key?(key)
66
- super(convert_key(key))
67
- end
68
-
69
- alias_method :include?, :key?
70
- alias_method :has_key?, :key?
71
- alias_method :member?, :key?
72
-
73
- # Fetches the value for the specified key, same as doing hash[key]
74
- def fetch(key, *extras)
75
- super(convert_key(key), *extras)
76
- end
77
-
78
- # Returns an array of the values at the specified indicies.
79
- def values_at(*indices)
80
- indices.collect {|key| self[convert_key(key)]}
81
- end
82
-
83
- # Returns an exact copy of the hash.
84
- def dup
85
- HashWithIndifferentAccess.new(self)
86
- end
87
-
88
- # Merges the instantized and the specified hashes together, giving precedence to the values from the second hash
89
- # Does not overwrite the existing hash.
90
- def merge(hash)
91
- self.dup.update(hash)
92
- end
93
-
94
- # Removes a specified key from the hash.
95
- def delete(key)
96
- super(convert_key(key))
97
- end
98
-
99
- def stringify_keys!; self end
100
- def symbolize_keys!; self end
101
- def to_options!; self end
102
-
103
- # Convert to a Hash with String keys.
104
- def to_hash
105
- Hash.new(default).merge(self)
106
- end
107
-
108
- protected
109
- def convert_key(key)
110
- key.kind_of?(Symbol) ? key.to_s : key
111
- end
112
-
113
- def convert_value(value)
114
- case value
115
- when Hash
116
- value.with_indifferent_access
117
- when Array
118
- value.collect { |e| e.is_a?(Hash) ? e.with_indifferent_access : e }
119
- else
120
- value
121
- end
122
- end
123
- end
124
-
125
- class Hash #:nodoc:
126
- def with_indifferent_access
127
- hash = HashWithIndifferentAccess.new(self)
128
- hash.default = self.default
129
- hash
130
- end
131
- end