Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

merged jrun and gwynms merb changes into main webrat code

  • Loading branch information...
commit dbb3883d2708879c2e0364914a47beb77a5e5a3c 2 parents 33fdf33 + 300880d
Rob Kaufman orangewolf authored
3  .gitignore
... ... @@ -1,5 +1,6 @@
1 1 coverage
  2 +.DS_Store
2 3 pkg
3 4 doc
4 5 ri
5   -email.txt
  6 +email.txt
12 README.txt
@@ -65,7 +65,10 @@ A test written with Webrat can handle these changes to these without any modific
65 65 Install
66 66 -------
67 67
68   -To install the latest release:
  68 +* Rails >= 1.2.6 or Merb edge
  69 +* Hpricot >= 0.6
  70 +* Rails integration tests in Test::Unit _or_
  71 +* RSpec stories (using an RSpec version >= revision 2997)
69 72
70 73 sudo gem install webrat
71 74
@@ -75,6 +78,13 @@ In your stories/helper.rb:
75 78
76 79 You could also unpack the gem into vendor/plugins.
77 80
  81 +To avoid losing sessions, you need this in environments/test.rb:
  82 +
  83 +Merb::Config.use do |c|
  84 + c[:session_store] = 'memory'
  85 +end
  86 +
  87 +== HISTORY:
78 88 Requirements
79 89 ------------
80 90
127 lib/boot_merb.rb
... ... @@ -0,0 +1,127 @@
  1 +module Webrat
  2 + module MerbTest
  3 +
  4 + #Our own redirect actions defined below, to deal with the fact that we need to store
  5 + #a controller reference.
  6 +
  7 + def current_page
  8 + @current_page ||= Webrat::Page.new(self)
  9 + end
  10 +
  11 + def current_page=(new_page)
  12 + @current_page = new_page
  13 + end
  14 +
  15 + # Issues a GET request for a page, follows any redirects, and verifies the final page
  16 + # load was successful.
  17 + #
  18 + # Example:
  19 + # visits "/"
  20 + def visits(*args)
  21 + @current_page = Webrat::Page.new(self, *args)
  22 + end
  23 +
  24 + def save_and_open_page
  25 + current_page.save_and_open
  26 + end
  27 +
  28 + [:reloads, :fills_in, :clicks_button, :selects, :chooses, :checks, :unchecks, :clicks_link, :clicks_link_within, :clicks_put_link, :clicks_get_link, :clicks_post_link, :clicks_delete_link].each do |method_name|
  29 + define_method(method_name) do |*args|
  30 + current_page.send(method_name, *args)
  31 + end
  32 + end
  33 +
  34 + #Session defines the following (used by webrat), but RspecStory doesn't. Merb's get/put/delete return a controller,
  35 + #which is where we get our status and response from.
  36 + #
  37 + #We have to preserve cookies like this, or the session is lost.
  38 + #
  39 + #While (in a web application) a PUT is modelled as a POST with a parameter _method,
  40 + #this close to the metal we need to make sure that we actually hit the underlying 'put' method,
  41 + #so we rewrite 'method'.
  42 + def request_via_redirect(method,path,parameters={},headers={})
  43 + method = parameters["_method"] if !parameters["_method"].blank?
  44 + mycookies = defined?(@controller) ? @controller.cookies : nil #will be nil if no requests yet
  45 + begin
  46 + @controller=self.send(method, path, parameters, headers) do |new_controller|
  47 + new_controller.cookies = mycookies
  48 + end
  49 + rescue => exception
  50 + raise unless exception.kind_of?(Merb::ControllerExceptions::Base)
  51 + #Now we want to go one level below 'post' to build the request ourselves, then send it to the controller
  52 + exception_klass = exception.class
  53 + klass = ::Exceptions rescue Merb::Controller
  54 + request = fake_request
  55 + request.params[:exception] = exception
  56 + request.params[:action] = exception_klass.name
  57 + @controller=dispatch_request(request, klass, exception_klass.name)
  58 + end
  59 +
  60 + follow_redirect! while redirect?
  61 + status
  62 + end
  63 +
  64 + def get_via_redirect(path, parameters = {}, headers = {})
  65 + request_via_redirect(:get,path,parameters,headers)
  66 + end
  67 +
  68 + def put_via_redirect(path, parameters = {}, headers = {})
  69 + request_via_redirect(:put,path,parameters,headers)
  70 + end
  71 +
  72 + def post_via_redirect(path, parameters = {}, headers = {})
  73 + request_via_redirect(:post,path,parameters,headers)
  74 + end
  75 +
  76 + def delete_via_redirect(path, parameters = {}, headers = {})
  77 + request_via_redirect(:delete,path,parameters,headers)
  78 + end
  79 +
  80 + def follow_redirect!
  81 + mycookies = @controller.cookies rescue nil
  82 + @controller=get @controller.headers["Location"] do |new_controller|
  83 + new_controller.cookies=mycookies
  84 + end
  85 + end
  86 +
  87 + def redirect?
  88 + [307, *(300..305)].include?(status)
  89 + end
  90 +
  91 + def status
  92 + @controller.status
  93 + end
  94 +
  95 + def response
  96 + @controller #things like @controller.body will work.
  97 + end
  98 +
  99 + def assert_response(resp)
  100 + if resp == :success
  101 + response.should be_successful
  102 + else
  103 + raise "assert_response #{resp.inspect} is not supported"
  104 + end
  105 + end
  106 +
  107 + end
  108 +end
  109 +
  110 +class Application < Merb::Controller
  111 + def cookies=(newcookies)
  112 + @_cookies = newcookies
  113 + end
  114 +end
  115 +
  116 +
  117 +#Other utilities used by Webrat that are present in Rails but not Merb. We can require heavy dependencies
  118 +#here because we're only loaded in Test mode.
  119 +require 'strscan'
  120 +require 'cgi'
  121 +require File.join(File.dirname(__FILE__), "merb_support", "param_parser.rb")
  122 +require File.join(File.dirname(__FILE__), "merb_support", "url_encoded_pair_parser.rb")
  123 +require File.join(File.dirname(__FILE__), "merb_support", "indifferent_access.rb")
  124 +require File.join(File.dirname(__FILE__), "merb_support", "support.rb")
  125 +
  126 +
  127 +
39 lib/boot_rails.rb
... ... @@ -0,0 +1,39 @@
  1 +module ActionController
  2 + module Integration
  3 + class Session
  4 +
  5 + unless instance_methods.include?("put_via_redirect")
  6 + include Webrat::RedirectActions
  7 + end
  8 +
  9 + def current_page
  10 + @current_page ||= Webrat::Page.new(self)
  11 + end
  12 +
  13 + def current_page=(new_page)
  14 + @current_page = new_page
  15 + end
  16 +
  17 + # Issues a GET request for a page, follows any redirects, and verifies the final page
  18 + # load was successful.
  19 + #
  20 + # Example:
  21 + # visits "/"
  22 + def visits(*args)
  23 + @current_page = Webrat::Page.new(self, *args)
  24 + end
  25 +
  26 + def save_and_open_page
  27 + current_page.save_and_open
  28 + end
  29 +
  30 + [:reloads, :fills_in, :clicks_button, :selects, :chooses, :checks, :unchecks, :clicks_link, :clicks_link_within, :clicks_put_link, :clicks_get_link, :clicks_post_link, :clicks_delete_link].each do |method_name|
  31 + define_method(method_name) do |*args|
  32 + current_page.send(method_name, *args)
  33 + end
  34 + end
  35 +
  36 + end
  37 + end
  38 +end
  39 +
125 lib/merb_support/indifferent_access.rb
... ... @@ -0,0 +1,125 @@
  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
  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 +
17 lib/merb_support/param_parser.rb
... ... @@ -0,0 +1,17 @@
  1 +module Webrat
  2 + class ParamParser
  3 + def self.parse_query_parameters(query_string)
  4 + return {} if query_string.blank?
  5 +
  6 + pairs = query_string.split('&').collect do |chunk|
  7 + next if chunk.empty?
  8 + key, value = chunk.split('=', 2)
  9 + next if key.empty?
  10 + value = value.nil? ? nil : CGI.unescape(value)
  11 + [ CGI.unescape(key), value ]
  12 + end.compact
  13 +
  14 + UrlEncodedPairParser.new(pairs).result
  15 + end
  16 + end
  17 +end
12 lib/merb_support/support.rb
... ... @@ -0,0 +1,12 @@
  1 +class Hash
  2 + def with_indifferent_access
  3 + hash = HashWithIndifferentAccess.new(self)
  4 + hash.default = self.default
  5 + hash
  6 + end
  7 +end
  8 +class NilClass
  9 + def to_param
  10 + nil
  11 + end
  12 +end
93 lib/merb_support/url_encoded_pair_parser.rb
... ... @@ -0,0 +1,93 @@
  1 +class UrlEncodedPairParser < StringScanner #:nodoc:
  2 + attr_reader :top, :parent, :result
  3 +
  4 + def initialize(pairs = [])
  5 + super('')
  6 + @result = {}
  7 + pairs.each { |key, value| parse(key, value) }
  8 + end
  9 +
  10 + KEY_REGEXP = %r{([^\[\]=&]+)}
  11 + BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
  12 +
  13 + # Parse the query string
  14 + def parse(key, value)
  15 + self.string = key
  16 + @top, @parent = result, nil
  17 +
  18 + # First scan the bare key
  19 + key = scan(KEY_REGEXP) or return
  20 + key = post_key_check(key)
  21 +
  22 + # Then scan as many nestings as present
  23 + until eos?
  24 + r = scan(BRACKETED_KEY_REGEXP) or return
  25 + key = self[1]
  26 + key = post_key_check(key)
  27 + end
  28 +
  29 + bind(key, value)
  30 + end
  31 +
  32 + private
  33 + # After we see a key, we must look ahead to determine our next action. Cases:
  34 + #
  35 + # [] follows the key. Then the value must be an array.
  36 + # = follows the key. (A value comes next)
  37 + # & or the end of string follows the key. Then the key is a flag.
  38 + # otherwise, a hash follows the key.
  39 + def post_key_check(key)
  40 + if scan(/\[\]/) # a[b][] indicates that b is an array
  41 + container(key, Array)
  42 + nil
  43 + elsif check(/\[[^\]]/) # a[b] indicates that a is a hash
  44 + container(key, Hash)
  45 + nil
  46 + else # End of key? We do nothing.
  47 + key
  48 + end
  49 + end
  50 +
  51 + # Add a container to the stack.
  52 + def container(key, klass)
  53 + type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
  54 + value = bind(key, klass.new)
  55 + type_conflict! klass, value unless value.is_a?(klass)
  56 + push(value)
  57 + end
  58 +
  59 + # Push a value onto the 'stack', which is actually only the top 2 items.
  60 + def push(value)
  61 + @parent, @top = @top, value
  62 + end
  63 +
  64 + # Bind a key (which may be nil for items in an array) to the provided value.
  65 + def bind(key, value)
  66 + if top.is_a? Array
  67 + if key
  68 + if top[-1].is_a?(Hash) && ! top[-1].key?(key)
  69 + top[-1][key] = value
  70 + else
  71 + top << {key => value}.with_indifferent_access
  72 + push top.last
  73 + value = top[key]
  74 + end
  75 + else
  76 + top << value
  77 + end
  78 + elsif top.is_a? Hash
  79 + key = CGI.unescape(key)
  80 + parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
  81 + top[key] ||= value
  82 + return top[key]
  83 + else
  84 + raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
  85 + end
  86 +
  87 + return value
  88 + end
  89 +
  90 + def type_conflict!(klass, value)
  91 + raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were #{value.inspect}.)"
  92 + end
  93 +end
11 lib/webrat.rb
... ... @@ -1,5 +1,8 @@
1 1 module Webrat
2   - VERSION = '0.2.1'
  2 + VERSION = '0.2.2'
  3 + def self.root
  4 + defined?(RAILS_ROOT) ? RAILS_ROOT : Merb.root
  5 + end
3 6 end
4 7
5 8 require "rubygems"
@@ -7,3 +10,9 @@ module Webrat
7 10
8 11 require File.dirname(__FILE__) + "/webrat/core"
9 12 require File.dirname(__FILE__) + "/webrat/rails" if defined?(RAILS_ENV)
  13 +
  14 +if defined?(Merb)
  15 + require File.join(File.dirname(__FILE__), "boot_merb.rb")
  16 +else
  17 + require File.join(File.dirname(__FILE__), "boot_rails.rb")
  18 +end
23 lib/webrat/core/field.rb
@@ -3,18 +3,17 @@ class Field
3 3
4 4 def self.class_for_element(element)
5 5 if element.name == "input"
6   - if %w[submit image].include?(element["type"])
  6 + if %w[submit image button].include?(element["type"])
7 7 field_class = "button"
8 8 else
9   - field_class = element["type"] || "text"
  9 + field_class = element["type"] || "text" #default type; 'type' attribute is not mandatory
10 10 end
11 11 else
12 12 field_class = element.name
13 13 end
14   -
15 14 Webrat.const_get("#{field_class.capitalize}Field")
16   - rescue NameError
17   - raise "Invalid field element: #{element.inspect}"
  15 + #rescue NameError
  16 + # raise "Invalid field element: #{element.inspect}"
18 17 end
19 18
20 19 def initialize(form, element)
@@ -108,10 +107,10 @@ def default_value
108 107 def param_parser
109 108 if defined?(CGIMethods)
110 109 CGIMethods
111   - else
112   - require "action_controller"
113   - require "action_controller/integration"
  110 + elsif defined?(ActionController::AbstractRequest)
114 111 ActionController::AbstractRequest
  112 + else
  113 + Webrat::ParamParser #used for Merb
115 114 end
116 115 end
117 116
@@ -141,6 +140,14 @@ def matches_text?(text)
141 140 def matches_value?(value)
142 141 @element["value"] =~ /^\W*#{Regexp.escape(value.to_s)}/i || matches_text?(value) || matches_alt?(value)
143 142 end
  143 +
  144 + def matches_id?(id)
  145 + @element["id"] =~ /^\W*#{Regexp.escape(id.to_s)}/i
  146 + end
  147 +
  148 + def matches_caption?(value)
  149 + @element.innerHTML =~ /^\W*#{Regexp.escape(value.to_s)}/i
  150 + end
144 151
145 152 def to_param
146 153 return nil if @value.nil?
13 lib/webrat/core/form.rb
@@ -34,9 +34,18 @@ def find_button(value = nil)
34 34 possible_buttons = fields_by_type([ButtonField])
35 35
36 36 possible_buttons.each do |possible_button|
  37 + return possible_button if possible_button.matches_id?(value)
  38 + end
  39 +
  40 + possible_buttons.each do |possible_button|
37 41 return possible_button if possible_button.matches_value?(value)
38 42 end
39 43
  44 + #If nothing matched on value, try by name.
  45 + possible_buttons.each do |possible_button|
  46 + return possible_button if possible_button.matches_caption?(value)
  47 + end
  48 +
40 49 nil
41 50 end
42 51
@@ -45,7 +54,7 @@ def fields
45 54
46 55 @fields = []
47 56
48   - (@element / "button, input, textarea, select").each do |field_element|
  57 + (@element / "input, textarea, select, button").each do |field_element|
49 58 @fields << Field.class_for_element(field_element).new(self, field_element)
50 59 end
51 60
@@ -123,7 +132,7 @@ def merge(all_params, new_param)
123 132 def merge_hash_values(a, b) # :nodoc:
124 133 a.keys.each do |k|
125 134 if b.has_key?(k)
126   - case [a[k], b[k]].map(&:class)
  135 + case [a[k].class, b[k].class]
127 136 when [Hash, Hash]
128 137 a[k] = merge_hash_values(a[k], b[k])
129 138 b.delete(k)
4 lib/webrat/core/logging.rb
@@ -3,12 +3,14 @@ module Logging
3 3
4 4 def debug_log(message) # :nodoc:
5 5 return unless logger
6   - logger.debug(message)
  6 + logger.debug message
7 7 end
8 8
9 9 def logger # :nodoc:
10 10 if defined? RAILS_DEFAULT_LOGGER
11 11 RAILS_DEFAULT_LOGGER
  12 + elsif defined? Merb
  13 + Merb.logger
12 14 else
13 15 nil
14 16 end
12 lib/webrat/core/scope.rb
@@ -82,10 +82,22 @@ def selects(option_text, options = {})
82 82 # along with the form. An optional <tt>content_type</tt> may be given.
83 83 #
84 84 # Example:
  85 +<<<<<<< HEAD:lib/webrat/core/scope.rb
85 86 # attaches_file "Resume", "/path/to/the/resume.txt"
86 87 # attaches_file "Photo", "/path/to/the/image.png", "image/png"
87 88 def attaches_file(id_or_name_or_label, path, content_type = nil)
88 89 find_field(id_or_name_or_label, FileField).set(path, content_type)
  90 +=======
  91 + # save_and_open
  92 + def save_and_open
  93 + return unless File.exist?(Webrat.root + "/tmp")
  94 +
  95 + filename = "webrat-#{Time.now.to_i}.html"
  96 + File.open(Webrat.root + "/tmp/#{filename}", "w") do |f|
  97 + f.write response.body
  98 + end
  99 + `open tmp/#{filename}`
  100 +>>>>>>> 300880db2f0d50a3e2d7b171eb9745cb50e1c534:lib/webrat/page.rb
89 101 end
90 102
91 103 alias_method :attach_file, :attaches_file
12 spec/api/fills_in_spec.rb
@@ -144,6 +144,18 @@
144 144 @session.fills_in "user[email]", :with => "foo@example.com"
145 145 @session.clicks_button
146 146 end
  147 +
  148 + def test_should_work_without_input_type
  149 + @response.stubs(:body).returns(<<-EOS)
  150 + <form method="post" action="/login">
  151 + <input id="user_email" name="user[email]" />
  152 + <input type="submit" />
  153 + </form>
  154 + EOS
  155 + @session.expects(:post_via_redirect).with("/login", "user" => {"email" => "foo@example.com"})
  156 + @session.fills_in "user[email]", :with => "foo@example.com"
  157 + @session.clicks_button
  158 + end
147 159
148 160 it "should work with symbols" do
149 161 @session.response_body = <<-EOS
11 spec/spec_helper.rb
@@ -4,13 +4,12 @@
4 4 # gem install redgreen for colored test output
5 5 begin require "redgreen" unless ENV['TM_CURRENT_LINE']; rescue LoadError; end
6 6
7   -require "active_support"
8   -
9   -silence_warnings do
10   - require "action_controller"
11   - require "action_controller/integration"
  7 +if ["rails","merb"].include?(ENV["TEST_MODE"])
  8 + require File.join(File.dirname(__FILE__), "helper_#{ENV["TEST_MODE"]}.rb")
  9 +else
  10 + raise "Please set the environment variable TEST_MODE to either 'rails' or 'merb'."
12 11 end
13   -
  12 +
14 13 require File.expand_path(File.dirname(__FILE__) + "/../lib/webrat")
15 14 require File.expand_path(File.dirname(__FILE__) + "/../lib/webrat/rails")
16 15 require File.dirname(__FILE__) + "/fakes/test_session"
11 test/helper_merb.rb
... ... @@ -0,0 +1,11 @@
  1 +require 'merb-core'
  2 +require 'merb_stories'
  3 +module Merb
  4 + module Test
  5 + class RspecStory
  6 + def flunk(message)
  7 + raise message
  8 + end
  9 + end
  10 + end
  11 +end
11 test/helper_rails.rb
... ... @@ -0,0 +1,11 @@
  1 +require "active_support"
  2 +silence_warnings do
  3 + require "action_controller"
  4 + require "action_controller/integration"
  5 +end
  6 +
  7 +class ActionController::Integration::Session
  8 + def flunk(message)
  9 + raise message
  10 + end
  11 +end

0 comments on commit dbb3883

Please sign in to comment.
Something went wrong with that request. Please try again.