webrat 0.4.1 → 0.4.2

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/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