Added webrat, thanks to jrun and gwynm for they're initial work on this
This commit is contained in:
parent
2fd734f9ff
commit
86879c13c0
@ -59,6 +59,13 @@ tests to break unnecessarily as your application evolves:
|
|||||||
|
|
||||||
A test written with Webrat can handle these changes to these without any modifications.
|
A test written with Webrat can handle these changes to these without any modifications.
|
||||||
|
|
||||||
|
=== Merb
|
||||||
|
To avoid losing sessions, you need this in environments/test.rb:
|
||||||
|
|
||||||
|
Merb::Config.use do |c|
|
||||||
|
c[:session_store] = 'memory'
|
||||||
|
end
|
||||||
|
|
||||||
=== Install
|
=== Install
|
||||||
|
|
||||||
To install the latest release:
|
To install the latest release:
|
||||||
|
15
Rakefile
15
Rakefile
@ -35,25 +35,36 @@ def remove_task(task_name)
|
|||||||
Rake.application.remove_task(task_name)
|
Rake.application.remove_task(task_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_file_list
|
||||||
|
if ENV['TEST_MODE'] == "merb"
|
||||||
|
list = FileList['spec/**/*_spec.rb']
|
||||||
|
list = list.find_all do |file| !file.match("rails") end
|
||||||
|
return list
|
||||||
|
else
|
||||||
|
return FileList['spec/**/*_spec.rb']
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
remove_task "test"
|
remove_task "test"
|
||||||
remove_task "test_deps"
|
remove_task "test_deps"
|
||||||
|
|
||||||
desc "Run all specs in spec directory"
|
desc "Run all specs in spec directory"
|
||||||
Spec::Rake::SpecTask.new do |t|
|
Spec::Rake::SpecTask.new do |t|
|
||||||
t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
|
t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
|
||||||
t.spec_files = FileList['spec/**/*_spec.rb']
|
t.spec_files = set_file_list
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "Run all specs in spec directory with RCov"
|
desc "Run all specs in spec directory with RCov"
|
||||||
Spec::Rake::SpecTask.new(:rcov) do |t|
|
Spec::Rake::SpecTask.new(:rcov) do |t|
|
||||||
t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
|
t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
|
||||||
t.spec_files = FileList['spec/**/*_spec.rb']
|
t.spec_files = set_file_list
|
||||||
t.rcov = true
|
t.rcov = true
|
||||||
t.rcov_opts = lambda do
|
t.rcov_opts = lambda do
|
||||||
IO.readlines(File.dirname(__FILE__) + "/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
|
IO.readlines(File.dirname(__FILE__) + "/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
require 'spec/rake/verify_rcov'
|
require 'spec/rake/verify_rcov'
|
||||||
RCov::VerifyTask.new(:verify_rcov => :rcov) do |t|
|
RCov::VerifyTask.new(:verify_rcov => :rcov) do |t|
|
||||||
t.threshold = 97.3 # Make sure you have rcov 0.7 or higher!
|
t.threshold = 97.3 # Make sure you have rcov 0.7 or higher!
|
||||||
|
@ -6,3 +6,4 @@ require "rubygems"
|
|||||||
|
|
||||||
require File.dirname(__FILE__) + "/webrat/core"
|
require File.dirname(__FILE__) + "/webrat/core"
|
||||||
require File.dirname(__FILE__) + "/webrat/rails" if defined?(RAILS_ENV)
|
require File.dirname(__FILE__) + "/webrat/rails" if defined?(RAILS_ENV)
|
||||||
|
require File.dirname(__FILE__) + "/webrat/merb" if defined?(Merb)
|
||||||
|
@ -9,6 +9,8 @@ module Webrat
|
|||||||
def logger # :nodoc:
|
def logger # :nodoc:
|
||||||
if defined? RAILS_DEFAULT_LOGGER
|
if defined? RAILS_DEFAULT_LOGGER
|
||||||
RAILS_DEFAULT_LOGGER
|
RAILS_DEFAULT_LOGGER
|
||||||
|
elsif defined? Merb
|
||||||
|
Merb.logger
|
||||||
else
|
else
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
45
lib/webrat/merb.rb
Normal file
45
lib/webrat/merb.rb
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
module Webrat
|
||||||
|
class Session
|
||||||
|
include Merb::Test::RequestHelper
|
||||||
|
|
||||||
|
attr_reader :response
|
||||||
|
|
||||||
|
def get(url, data, headers = nil)
|
||||||
|
do_request(url, data, headers, "GET")
|
||||||
|
end
|
||||||
|
|
||||||
|
def post(url, data, headers = nil)
|
||||||
|
do_request(url, data, headers, "POST")
|
||||||
|
end
|
||||||
|
|
||||||
|
def put(url, data, headers = nil)
|
||||||
|
do_request(url, data, headers, "PUT")
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(url, data, headers = nil)
|
||||||
|
do_request(url, data, headers, "DELETE")
|
||||||
|
end
|
||||||
|
|
||||||
|
def response_body
|
||||||
|
@response.body.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def response_code
|
||||||
|
@response.status
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
def do_request(url, data, headers, method)
|
||||||
|
@response = request(url, :params => data, :headers => headers, :method => method)
|
||||||
|
self.get(@response.headers['Location'], nil, @response.headers) if @response.status == 302
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Merb::Test::RspecStory
|
||||||
|
def browser
|
||||||
|
@browser ||= Webrat::Session.new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
125
lib/webrat/merb/indifferent_access.rb
Normal file
125
lib/webrat/merb/indifferent_access.rb
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
|
17
lib/webrat/merb/param_parser.rb
Normal file
17
lib/webrat/merb/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
|
12
lib/webrat/merb/support.rb
Normal file
12
lib/webrat/merb/support.rb
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
class Hash
|
||||||
|
def with_indifferent_access
|
||||||
|
hash = HashWithIndifferentAccess.new(self)
|
||||||
|
hash.default = self.default
|
||||||
|
hash
|
||||||
|
end
|
||||||
|
end
|
||||||
|
class NilClass
|
||||||
|
def to_param
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
93
lib/webrat/merb/url_encoded_pair_parser.rb
Normal file
93
lib/webrat/merb/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
|
@ -1,5 +1,5 @@
|
|||||||
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
|
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
|
||||||
|
unless ENV["TEST_MODE"] == "merb" #TODO - Rob
|
||||||
describe "attaches_file" do
|
describe "attaches_file" do
|
||||||
before do
|
before do
|
||||||
@session = Webrat::TestSession.new
|
@session = Webrat::TestSession.new
|
||||||
@ -70,3 +70,4 @@ describe "attaches_file" do
|
|||||||
@session.clicks_button
|
@session.clicks_button
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
@ -5,16 +5,18 @@ require "spec/interop/test"
|
|||||||
# gem install redgreen for colored test output
|
# gem install redgreen for colored test output
|
||||||
begin require "redgreen" unless ENV['TM_CURRENT_LINE']; rescue LoadError; end
|
begin require "redgreen" unless ENV['TM_CURRENT_LINE']; rescue LoadError; end
|
||||||
|
|
||||||
require "active_support"
|
require File.expand_path(File.dirname(__FILE__) + "/../lib/webrat")
|
||||||
|
require File.dirname(__FILE__) + "/fakes/test_session"
|
||||||
|
|
||||||
silence_warnings do
|
if ["rails","merb"].include?(ENV["TEST_MODE"])
|
||||||
require "action_controller"
|
require File.join(File.dirname(__FILE__), "webrat", "#{ENV["TEST_MODE"]}", "helper.rb")
|
||||||
require "action_controller/integration"
|
else
|
||||||
|
puts "Assuming test mode is Rails... for Merb set TEST_MODE=merb and rerun."
|
||||||
|
ENV["TEST_MODE"] = 'rails'
|
||||||
|
require File.join(File.dirname(__FILE__), "webrat", "#{ENV["TEST_MODE"]}", "helper.rb")
|
||||||
end
|
end
|
||||||
|
|
||||||
require File.expand_path(File.dirname(__FILE__) + "/../lib/webrat")
|
|
||||||
require File.expand_path(File.dirname(__FILE__) + "/../lib/webrat/rails")
|
|
||||||
require File.dirname(__FILE__) + "/fakes/test_session"
|
|
||||||
|
|
||||||
Spec::Runner.configure do |config|
|
Spec::Runner.configure do |config|
|
||||||
# Nothing to configure yet
|
# Nothing to configure yet
|
||||||
|
2
spec/webrat/merb/helper.rb
Normal file
2
spec/webrat/merb/helper.rb
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
require 'merb-core'
|
||||||
|
require File.expand_path(File.dirname(__FILE__) + "/../../../lib/webrat/merb")
|
7
spec/webrat/rails/helper.rb
Normal file
7
spec/webrat/rails/helper.rb
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
require "active_support"
|
||||||
|
|
||||||
|
silence_warnings do
|
||||||
|
require "action_controller"
|
||||||
|
require "action_controller/integration"
|
||||||
|
end
|
||||||
|
require File.expand_path(File.dirname(__FILE__) + "/../../../lib/webrat/rails")
|
Loading…
Reference in New Issue
Block a user