Added Merb support.
This commit is contained in:
parent
13b0ec75f4
commit
cdafd2ec0f
28
Manifest.txt
28
Manifest.txt
@ -1,26 +1,3 @@
|
||||
.git/HEAD
|
||||
.git/config
|
||||
.git/description
|
||||
.git/hooks/applypatch-msg
|
||||
.git/hooks/commit-msg
|
||||
.git/hooks/post-commit
|
||||
.git/hooks/post-receive
|
||||
.git/hooks/post-update
|
||||
.git/hooks/pre-applypatch
|
||||
.git/hooks/pre-commit
|
||||
.git/hooks/pre-rebase
|
||||
.git/hooks/update
|
||||
.git/index
|
||||
.git/info/exclude
|
||||
.git/logs/HEAD
|
||||
.git/logs/refs/heads/master
|
||||
.git/logs/refs/remotes/origin/master
|
||||
.git/objects/pack/pack-3243faf8e8f6f0e226adf39a2c2e8c19c18da6c4.idx
|
||||
.git/objects/pack/pack-3243faf8e8f6f0e226adf39a2c2e8c19c18da6c4.pack
|
||||
.git/refs/heads/master
|
||||
.git/refs/remotes/origin/HEAD
|
||||
.git/refs/remotes/origin/master
|
||||
.gitignore
|
||||
History.txt
|
||||
MIT-LICENSE.txt
|
||||
Manifest.txt
|
||||
@ -38,6 +15,11 @@ lib/webrat/logging.rb
|
||||
lib/webrat/page.rb
|
||||
lib/webrat/redirect_actions.rb
|
||||
lib/webrat/select_option.rb
|
||||
lib/boot_merb.rb
|
||||
lib/boot_rails.rb
|
||||
lib/merb_support/indifferent_access.rb
|
||||
lib/merb_support/param_parser.rb
|
||||
lib/merb_support/url_encoded_pair_parser.rb
|
||||
test/checks_test.rb
|
||||
test/chooses_test.rb
|
||||
test/clicks_button_test.rb
|
||||
|
84
lib/boot_merb.rb
Normal file
84
lib/boot_merb.rb
Normal file
@ -0,0 +1,84 @@
|
||||
#In Merb, we have an RspecStory instead of an integration Session.
|
||||
class Merb::Test::RspecStory
|
||||
|
||||
#Our own redirect actions defined below, to deal with the fact that we need to store
|
||||
#a controller reference.
|
||||
|
||||
def current_page
|
||||
@current_page ||= Webrat::Page.new(self)
|
||||
end
|
||||
|
||||
def current_page=(new_page)
|
||||
@current_page = new_page
|
||||
end
|
||||
|
||||
# Issues a GET request for a page, follows any redirects, and verifies the final page
|
||||
# load was successful.
|
||||
#
|
||||
# Example:
|
||||
# visits "/"
|
||||
def visits(*args)
|
||||
@current_page = Webrat::Page.new(self, *args)
|
||||
end
|
||||
|
||||
def save_and_open_page
|
||||
current_page.save_and_open
|
||||
end
|
||||
|
||||
[: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|
|
||||
define_method(method_name) do |*args|
|
||||
current_page.send(method_name, *args)
|
||||
end
|
||||
end
|
||||
|
||||
#Session defines the following (used by webrat), but RspecStory doesn't. Merb's get/put/delete return a controller,
|
||||
#which is where we get our status and response from.
|
||||
def get_via_redirect(path, parameters = {}, headers = {})
|
||||
@controller=get path, parameters, headers
|
||||
follow_redirect! while redirect?
|
||||
status
|
||||
end
|
||||
def put_via_redirect(path, parameters = {}, headers = {})
|
||||
@controller=put path, parameters, headers
|
||||
follow_redirect! while redirect?
|
||||
status
|
||||
end
|
||||
def delete_via_redirect(path, parameters = {}, headers = {})
|
||||
@controller=delete path, parameters, headers
|
||||
follow_redirect! while redirect?
|
||||
status
|
||||
end
|
||||
|
||||
def follow_redirect!
|
||||
@controller=get @controller.headers["Location"]
|
||||
end
|
||||
|
||||
def redirect?
|
||||
[307, *(300..305)].include?(status)
|
||||
end
|
||||
|
||||
def status
|
||||
@controller.status
|
||||
end
|
||||
|
||||
def response
|
||||
@controller #things like @controller.body will work.
|
||||
end
|
||||
|
||||
def assert_response(resp)
|
||||
if resp == :success
|
||||
response.should be_successful
|
||||
else
|
||||
raise "assert_response #{resp.inspect} is not supported"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#Other utilities used by Webrat that are present in Rails but not Merb. We can require heavy dependencies
|
||||
#here because we're only loaded in Test mode.
|
||||
require 'strscan'
|
||||
require 'cgi'
|
||||
require File.join(File.dirname(__FILE__), "merb_support", "param_parser.rb")
|
||||
require File.join(File.dirname(__FILE__), "merb_support", "url_encoded_pair_parser.rb")
|
||||
require File.join(File.dirname(__FILE__), "merb_support", "indifferent_access.rb")
|
||||
|
39
lib/boot_rails.rb
Normal file
39
lib/boot_rails.rb
Normal file
@ -0,0 +1,39 @@
|
||||
module ActionController
|
||||
module Integration
|
||||
class Session
|
||||
|
||||
unless instance_methods.include?("put_via_redirect")
|
||||
include Webrat::RedirectActions
|
||||
end
|
||||
|
||||
def current_page
|
||||
@current_page ||= Webrat::Page.new(self)
|
||||
end
|
||||
|
||||
def current_page=(new_page)
|
||||
@current_page = new_page
|
||||
end
|
||||
|
||||
# Issues a GET request for a page, follows any redirects, and verifies the final page
|
||||
# load was successful.
|
||||
#
|
||||
# Example:
|
||||
# visits "/"
|
||||
def visits(*args)
|
||||
@current_page = Webrat::Page.new(self, *args)
|
||||
end
|
||||
|
||||
def save_and_open_page
|
||||
current_page.save_and_open
|
||||
end
|
||||
|
||||
[: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|
|
||||
define_method(method_name) do |*args|
|
||||
current_page.send(method_name, *args)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
138
lib/merb_support/indifferent_access.rb
Normal file
138
lib/merb_support/indifferent_access.rb
Normal file
@ -0,0 +1,138 @@
|
||||
# This class has dubious semantics and we only have it so that
|
||||
# people can write params[:key] instead of params['key']
|
||||
# and they get the same value for both keys.
|
||||
|
||||
class HashWithIndifferentAccess < Hash
|
||||
def initialize(constructor = {})
|
||||
if constructor.is_a?(Hash)
|
||||
super()
|
||||
update(constructor)
|
||||
else
|
||||
super(constructor)
|
||||
end
|
||||
end
|
||||
|
||||
def default(key = nil)
|
||||
if key.is_a?(Symbol) && include?(key = key.to_s)
|
||||
self[key]
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
|
||||
alias_method :regular_update, :update unless method_defined?(:regular_update)
|
||||
|
||||
#
|
||||
# Assigns a new value to the hash.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# hash = HashWithIndifferentAccess.new
|
||||
# hash[:key] = "value"
|
||||
#
|
||||
def []=(key, value)
|
||||
regular_writer(convert_key(key), convert_value(value))
|
||||
end
|
||||
|
||||
#
|
||||
# Updates the instantized hash with values from the second.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# >> hash_1 = HashWithIndifferentAccess.new
|
||||
# => {}
|
||||
#
|
||||
# >> hash_1[:key] = "value"
|
||||
# => "value"
|
||||
#
|
||||
# >> hash_2 = HashWithIndifferentAccess.new
|
||||
# => {}
|
||||
#
|
||||
# >> hash_2[:key] = "New Value!"
|
||||
# => "New Value!"
|
||||
#
|
||||
# >> hash_1.update(hash_2)
|
||||
# => {"key"=>"New Value!"}
|
||||
#
|
||||
def update(other_hash)
|
||||
other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
|
||||
self
|
||||
end
|
||||
|
||||
alias_method :merge!, :update
|
||||
|
||||
# Checks the hash for a key matching the argument passed in
|
||||
def key?(key)
|
||||
super(convert_key(key))
|
||||
end
|
||||
|
||||
alias_method :include?, :key?
|
||||
alias_method :has_key?, :key?
|
||||
alias_method :member?, :key?
|
||||
|
||||
# Fetches the value for the specified key, same as doing hash[key]
|
||||
def fetch(key, *extras)
|
||||
super(convert_key(key), *extras)
|
||||
end
|
||||
|
||||
# Returns an array of the values at the specified indicies.
|
||||
def values_at(*indices)
|
||||
indices.collect {|key| self[convert_key(key)]}
|
||||
end
|
||||
|
||||
# Returns an exact copy of the hash.
|
||||
def dup
|
||||
HashWithIndifferentAccess.new(self)
|
||||
end
|
||||
|
||||
# Merges the instantized and the specified hashes together, giving precedence to the values from the second hash
|
||||
# Does not overwrite the existing hash.
|
||||
def merge(hash)
|
||||
self.dup.update(hash)
|
||||
end
|
||||
|
||||
# Removes a specified key from the hash.
|
||||
def delete(key)
|
||||
super(convert_key(key))
|
||||
end
|
||||
|
||||
def stringify_keys!; self end
|
||||
def symbolize_keys!; self end
|
||||
def to_options!; self end
|
||||
|
||||
# Convert to a Hash with String keys.
|
||||
def to_hash
|
||||
Hash.new(default).merge(self)
|
||||
end
|
||||
|
||||
protected
|
||||
def convert_key(key)
|
||||
key.kind_of?(Symbol) ? key.to_s : key
|
||||
end
|
||||
|
||||
def convert_value(value)
|
||||
case value
|
||||
when Hash
|
||||
value.with_indifferent_access
|
||||
when Array
|
||||
value.collect { |e| e.is_a?(Hash) ? e.with_indifferent_access : e }
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ActiveSupport #:nodoc:
|
||||
module CoreExtensions #:nodoc:
|
||||
module Hash #:nodoc:
|
||||
module IndifferentAccess #:nodoc:
|
||||
def with_indifferent_access
|
||||
hash = HashWithIndifferentAccess.new(self)
|
||||
hash.default = self.default
|
||||
hash
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
17
lib/merb_support/param_parser.rb
Normal file
17
lib/merb_support/param_parser.rb
Normal file
@ -0,0 +1,17 @@
|
||||
module Webrat
|
||||
class ParamParser
|
||||
def self.parse_query_parameters(query_string)
|
||||
return {} if query_string.blank?
|
||||
|
||||
pairs = query_string.split('&').collect do |chunk|
|
||||
next if chunk.empty?
|
||||
key, value = chunk.split('=', 2)
|
||||
next if key.empty?
|
||||
value = value.nil? ? nil : CGI.unescape(value)
|
||||
[ CGI.unescape(key), value ]
|
||||
end.compact
|
||||
|
||||
UrlEncodedPairParser.new(pairs).result
|
||||
end
|
||||
end
|
||||
end
|
93
lib/merb_support/url_encoded_pair_parser.rb
Normal file
93
lib/merb_support/url_encoded_pair_parser.rb
Normal file
@ -0,0 +1,93 @@
|
||||
class UrlEncodedPairParser < StringScanner #:nodoc:
|
||||
attr_reader :top, :parent, :result
|
||||
|
||||
def initialize(pairs = [])
|
||||
super('')
|
||||
@result = {}
|
||||
pairs.each { |key, value| parse(key, value) }
|
||||
end
|
||||
|
||||
KEY_REGEXP = %r{([^\[\]=&]+)}
|
||||
BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
|
||||
|
||||
# Parse the query string
|
||||
def parse(key, value)
|
||||
self.string = key
|
||||
@top, @parent = result, nil
|
||||
|
||||
# First scan the bare key
|
||||
key = scan(KEY_REGEXP) or return
|
||||
key = post_key_check(key)
|
||||
|
||||
# Then scan as many nestings as present
|
||||
until eos?
|
||||
r = scan(BRACKETED_KEY_REGEXP) or return
|
||||
key = self[1]
|
||||
key = post_key_check(key)
|
||||
end
|
||||
|
||||
bind(key, value)
|
||||
end
|
||||
|
||||
private
|
||||
# After we see a key, we must look ahead to determine our next action. Cases:
|
||||
#
|
||||
# [] follows the key. Then the value must be an array.
|
||||
# = follows the key. (A value comes next)
|
||||
# & or the end of string follows the key. Then the key is a flag.
|
||||
# otherwise, a hash follows the key.
|
||||
def post_key_check(key)
|
||||
if scan(/\[\]/) # a[b][] indicates that b is an array
|
||||
container(key, Array)
|
||||
nil
|
||||
elsif check(/\[[^\]]/) # a[b] indicates that a is a hash
|
||||
container(key, Hash)
|
||||
nil
|
||||
else # End of key? We do nothing.
|
||||
key
|
||||
end
|
||||
end
|
||||
|
||||
# Add a container to the stack.
|
||||
def container(key, klass)
|
||||
type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
|
||||
value = bind(key, klass.new)
|
||||
type_conflict! klass, value unless value.is_a?(klass)
|
||||
push(value)
|
||||
end
|
||||
|
||||
# Push a value onto the 'stack', which is actually only the top 2 items.
|
||||
def push(value)
|
||||
@parent, @top = @top, value
|
||||
end
|
||||
|
||||
# Bind a key (which may be nil for items in an array) to the provided value.
|
||||
def bind(key, value)
|
||||
if top.is_a? Array
|
||||
if key
|
||||
if top[-1].is_a?(Hash) && ! top[-1].key?(key)
|
||||
top[-1][key] = value
|
||||
else
|
||||
top << {key => value}.with_indifferent_access
|
||||
push top.last
|
||||
value = top[key]
|
||||
end
|
||||
else
|
||||
top << value
|
||||
end
|
||||
elsif top.is_a? Hash
|
||||
key = CGI.unescape(key)
|
||||
parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
|
||||
top[key] ||= value
|
||||
return top[key]
|
||||
else
|
||||
raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
def type_conflict!(klass, value)
|
||||
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}.)"
|
||||
end
|
||||
end
|
@ -6,42 +6,8 @@ module Webrat
|
||||
VERSION = '0.2.1'
|
||||
end
|
||||
|
||||
module ActionController
|
||||
module Integration
|
||||
class Session
|
||||
|
||||
unless instance_methods.include?("put_via_redirect")
|
||||
include Webrat::RedirectActions
|
||||
if defined?(Merb)
|
||||
require File.join(File.dirname(__FILE__), "boot_merb.rb")
|
||||
else
|
||||
require File.join(File.dirname(__FILE__), "boot_rails.rb")
|
||||
end
|
||||
|
||||
def current_page
|
||||
@current_page ||= Webrat::Page.new(self)
|
||||
end
|
||||
|
||||
def current_page=(new_page)
|
||||
@current_page = new_page
|
||||
end
|
||||
|
||||
# Issues a GET request for a page, follows any redirects, and verifies the final page
|
||||
# load was successful.
|
||||
#
|
||||
# Example:
|
||||
# visits "/"
|
||||
def visits(*args)
|
||||
@current_page = Webrat::Page.new(self, *args)
|
||||
end
|
||||
|
||||
def save_and_open_page
|
||||
current_page.save_and_open
|
||||
end
|
||||
|
||||
[: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|
|
||||
define_method(method_name) do |*args|
|
||||
current_page.send(method_name, *args)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -6,15 +6,15 @@ module Webrat
|
||||
if %w[submit image].include?(element["type"])
|
||||
field_class = "button"
|
||||
else
|
||||
field_class = element["type"]
|
||||
field_class = element["type"] || "text" #default type; 'type' attribute is not mandatory
|
||||
end
|
||||
else
|
||||
field_class = element.name
|
||||
end
|
||||
|
||||
Webrat.const_get("#{field_class.capitalize}Field")
|
||||
rescue NameError
|
||||
raise "Invalid field element: #{element.inspect}"
|
||||
#rescue NameError
|
||||
# raise "Invalid field element: #{element.inspect}"
|
||||
end
|
||||
|
||||
def initialize(form, element)
|
||||
@ -92,8 +92,10 @@ module Webrat
|
||||
def param_parser
|
||||
if defined?(CGIMethods)
|
||||
CGIMethods
|
||||
else
|
||||
elsif defined?(ActionController::AbstractRequest)
|
||||
ActionController::AbstractRequest
|
||||
else
|
||||
Webrat::ParamParser #used for Merb
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -3,12 +3,14 @@ module Webrat
|
||||
|
||||
def debug_log(message) # :nodoc:
|
||||
return unless logger
|
||||
logger.debug
|
||||
logger.debug message
|
||||
end
|
||||
|
||||
def logger # :nodoc:
|
||||
if defined? RAILS_DEFAULT_LOGGER
|
||||
RAILS_DEFAULT_LOGGER
|
||||
elsif defined? Merb
|
||||
Merb.logger
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
@ -138,6 +138,18 @@ class FillsInTest < Test::Unit::TestCase
|
||||
@session.clicks_button
|
||||
end
|
||||
|
||||
def test_should_work_without_input_type
|
||||
@response.stubs(:body).returns(<<-EOS)
|
||||
<form method="post" action="/login">
|
||||
<input id="user_email" name="user[email]" />
|
||||
<input type="submit" />
|
||||
</form>
|
||||
EOS
|
||||
@session.expects(:post_via_redirect).with("/login", "user" => {"email" => "foo@example.com"})
|
||||
@session.fills_in "user[email]", :with => "foo@example.com"
|
||||
@session.clicks_button
|
||||
end
|
||||
|
||||
def test_should_work_with_symbols
|
||||
@response.stubs(:body).returns(<<-EOS)
|
||||
<form method="post" action="/login">
|
||||
|
Loading…
Reference in New Issue
Block a user