Next step in upgrading Tracks to Rails 2.2. Some highlights:

* Ran rake rails:update
* Added old actionwebservice framework
* Updated RSpec and RSpec-Rails
* Removed asset_packager plugin (not compatible, Scott no longer maintaining), and replaced with bundle_fu. See the bundle_fu README for more info.
* Hacks to UJS and ARTS plugins, which are no longer supported. Probably should move off both UJS and RJS.
* Hack to flashobject_helper plugin (upgrade to Rails 2.2-compatible version if/when it comes out.)
* Hack to skinny-spec plugin, for Rails 2.2 compatibility. Should check for official release.
* Hacks to resource_feeder plugin, for Rails 2.2 compatibility. Should check for official release (not likely) or move off it.
* Addressed some deprecation warnings. More to come.
* My mobile mime type hackery is no longer necessary with new Rails features. Yay!
* Updated environment.rb.tmpl with changes

TODO:
* Restore view specs marked pending
* Fix failing integration tests.
* Try selenium tests.
* Investigate OpenID support.
* Address deprecation warnings.
* Consider moving parts of environment.rb to initializers
* Address annoying config.gem warning about highline gem
This commit is contained in:
Luke Melia 2008-11-29 12:00:06 -05:00
parent 6d11ebd1b0
commit 35ae5fc431
394 changed files with 15184 additions and 9936 deletions

View file

@ -9,6 +9,7 @@ require "spec/rails/example/functional_example_group"
require "spec/rails/example/controller_example_group"
require "spec/rails/example/helper_example_group"
require "spec/rails/example/view_example_group"
require "spec/rails/example/cookies_proxy"
module Spec
module Rails

View file

@ -2,27 +2,24 @@ module Spec
module Rails
module Example
class AssignsHashProxy #:nodoc:
def initialize(object)
@object = object
def initialize(example_group, &block)
@target = block.call
@example_group = example_group
end
def [](ivar)
if assigns.include?(ivar.to_s)
assigns[ivar.to_s]
elsif assigns.include?(ivar)
assigns[ivar]
else
nil
end
def [](key)
return false if assigns[key] == false
return false if assigns[key.to_s] == false
assigns[key] || assigns[key.to_s] || @target.instance_variable_get("@#{key}")
end
def []=(ivar, val)
@object.instance_variable_set "@#{ivar}", val
assigns[ivar.to_s] = val
def []=(key, val)
@target.instance_variable_set("@#{key}", val)
end
def delete(name)
assigns.delete(name.to_s)
def delete(key)
assigns.delete(key.to_s)
@target.instance_variable_set("@#{key}", nil)
end
def each(&block)
@ -35,7 +32,7 @@ module Spec
protected
def assigns
@object.assigns
@example_group.orig_assigns
end
end
end

View file

@ -125,7 +125,7 @@ module Spec
attr_reader :response, :request, :controller
def initialize(defined_description, &implementation) #:nodoc:
def initialize(defined_description, options={}, &implementation) #:nodoc:
super
controller_class_name = self.class.controller_class_name
if controller_class_name
@ -158,7 +158,9 @@ module Spec
protected
def _assigns_hash_proxy
@_assigns_hash_proxy ||= AssignsHashProxy.new @controller
@_assigns_hash_proxy ||= AssignsHashProxy.new self do
@response.template
end
end
private
@ -183,61 +185,39 @@ module Spec
unless integrate_views?
if @template.respond_to?(:finder)
(class << @template.finder; self; end).class_eval do
define_method :file_exists? do
true
end
define_method :file_exists? do; true; end
end
else
(class << @template; self; end).class_eval do
define_method :file_exists? do
true
end
define_method :file_exists? do; true; end
end
end
(class << @template; self; end).class_eval do
define_method :render_file do |*args|
@first_render ||= args[0]
@first_render ||= args[0] unless args[0] =~ /^layouts/
@_first_render ||= args[0] unless args[0] =~ /^layouts/
end
define_method :_pick_template do |*args|
@_first_render ||= args[0] unless args[0] =~ /^layouts/
PickedTemplate.new
end
end
end
end
if matching_message_expectation_exists(options)
expect_render_mock_proxy.render(options, &block)
render_proxy.render(options, &block)
@performed_render = true
else
unless matching_stub_exists(options)
if matching_stub_exists(options)
@performed_render = true
else
super(options, deprecated_status_or_extra_options, &block)
end
end
end
def raise_with_disable_message(old_method, new_method)
raise %Q|
controller.#{old_method}(:render) has been disabled because it
can often produce unexpected results. Instead, you should
use the following (before the action):
controller.#{new_method}(*args)
See the rdoc for #{new_method} for more information.
|
end
def should_receive(*args)
if args[0] == :render
raise_with_disable_message("should_receive", "expect_render")
else
super
end
end
def stub!(*args)
if args[0] == :render
raise_with_disable_message("stub!", "stub_render")
else
super
end
end
def response(&block)
# NOTE - we're setting @update for the assert_select_spec - kinda weird, huh?
@update = block
@ -255,17 +235,22 @@ module Spec
end
def matching_message_expectation_exists(options)
expect_render_mock_proxy.send(:__mock_proxy).send(:find_matching_expectation, :render, options)
render_proxy.send(:__mock_proxy).send(:find_matching_expectation, :render, options)
end
def matching_stub_exists(options)
expect_render_mock_proxy.send(:__mock_proxy).send(:find_matching_method_stub, :render, options)
render_proxy.send(:__mock_proxy).send(:find_matching_method_stub, :render, options)
end
end
Spec::Example::ExampleGroupFactory.register(:controller, self)
end
class PickedTemplate
def render_template(*ignore_args); end
def render_partial(*ignore_args); end
end
end
end
end

View file

@ -0,0 +1,25 @@
require 'action_controller/cookies'
module Spec
module Rails
module Example
class CookiesProxy
def initialize(example)
@example = example
end
def[]=(name, value)
@example.request.cookies[name.to_s] = CGI::Cookie.new(name.to_s, value)
end
def [](name)
@example.response.cookies[name.to_s]
end
def delete(name)
@example.response.cookies.delete(name.to_s)
end
end
end
end
end

View file

@ -27,6 +27,33 @@ module Spec
def session
response.session
end
# Overrides the <tt>cookies()</tt> method in
# ActionController::TestResponseBehaviour, returning a proxy that
# accesses the requests cookies when setting a cookie and the
# responses cookies when reading one. This allows you to set and read
# cookies in examples using the same API with which you set and read
# them in controllers.
#
# == Examples (Rails >= 1.2.6)
#
# cookies[:user_id] = '1234'
# get :index
# assigns[:user].id.should == '1234'
#
# post :login
# cookies[:login].expires.should == 1.week.from_now
#
# == Examples (Rails >= 2.0.0 only)
#
# cookies[:user_id] = {:value => '1234', :expires => 1.minute.ago}
# get :index
# response.should be_redirect
def cookies
@cookies ||= Spec::Rails::Example::CookiesProxy.new(self)
end
alias_method :orig_assigns, :assigns
# :call-seq:
# assigns()
@ -53,6 +80,7 @@ module Spec
_assigns_hash_proxy[key]
end
end
end
end
end

View file

@ -148,7 +148,9 @@ module Spec
protected
def _assigns_hash_proxy
@_assigns_hash_proxy ||= AssignsHashProxy.new helper
@_assigns_hash_proxy ||= AssignsHashProxy.new self do
helper
end
end
end

View file

@ -11,11 +11,15 @@ module Spec
class RailsExampleGroup < Test::Unit::TestCase
# Rails >= r8570 uses setup/teardown_fixtures explicitly
before(:each) do
setup_fixtures if self.respond_to?(:setup_fixtures)
end
after(:each) do
teardown_fixtures if self.respond_to?(:teardown_fixtures)
# However, Rails >= r8664 extracted these out to use ActiveSupport::Callbacks.
# The latter case is handled at the TestCase level, in interop/testcase.rb
unless ActiveSupport.const_defined?(:Callbacks) and self.include?(ActiveSupport::Callbacks)
before(:each) do
setup_fixtures if self.respond_to?(:setup_fixtures)
end
after(:each) do
teardown_fixtures if self.respond_to?(:teardown_fixtures)
end
end
include Spec::Rails::Matchers

View file

@ -3,61 +3,39 @@ require 'spec/mocks/framework'
module Spec
module Rails
module Example
# Provides specialized mock-like behaviour for controller and view examples,
# allowing you to mock or stub calls to render with specific arguments while
# ignoring all other calls.
# Extends the #should_receive, #should_not_receive and #stub! methods in rspec's
# mocking framework to handle #render calls to controller in controller examples
# and template and view examples
module RenderObserver
# Similar to mocking +render+ with the exception that calls to +render+ with
# any other options are passed on to the receiver (i.e. controller in
# controller examples, template in view examples).
#
# This is necessary because Rails uses the +render+ method on both
# controllers and templates as a dispatcher to render different kinds of
# things, sometimes resulting in many calls to the render method within one
# request. This approach makes it impossible to use a normal mock object, which
# is designed to observe all incoming messages with a given name.
#
# +expect_render+ is auto-verifying, so failures will be reported without
# requiring you to explicitly request verification.
#
# Also, +expect_render+ uses parts of RSpec's mock expectation framework. Because
# it wraps only a subset of the framework, using this will create no conflict with
# other mock frameworks if you choose to use them. Additionally, the object returned
# by expect_render is an RSpec mock object, which means that you can call any of the
# chained methods available in RSpec's mocks.
#
# == Controller Examples
#
# controller.expect_render(:partial => 'thing', :object => thing)
# controller.expect_render(:partial => 'thing', :collection => things).once
#
# controller.stub_render(:partial => 'thing', :object => thing)
# controller.stub_render(:partial => 'thing', :collection => things).twice
#
# == View Examples
#
# template.expect_render(:partial => 'thing', :object => thing)
# template.expect_render(:partial => 'thing', :collection => things)
#
# template.stub_render(:partial => 'thing', :object => thing)
# template.stub_render(:partial => 'thing', :collection => things)
# DEPRECATED
#
# Use should_receive(:render).with(opts) instead
def expect_render(opts={})
warn_deprecation("expect_render", "should_receive")
register_verify_after_each
expect_render_mock_proxy.should_receive(:render, :expected_from => caller(1)[0]).with(opts)
render_proxy.should_receive(:render, :expected_from => caller(1)[0]).with(opts)
end
# This is exactly like expect_render, with the exception that the call to render will not
# be verified. Use this if you are trying to isolate your example from a complicated render
# operation but don't care whether it is called or not.
# DEPRECATED
#
# Use stub!(:render).with(opts) instead
def stub_render(opts={})
warn_deprecation("stub_render", "stub!")
register_verify_after_each
expect_render_mock_proxy.stub!(:render, :expected_from => caller(1)[0]).with(opts)
render_proxy.stub!(:render, :expected_from => caller(1)[0]).with(opts)
end
def warn_deprecation(deprecated_method, new_method)
Kernel.warn <<-WARNING
#{deprecated_method} is deprecated and will be removed from a future version of rspec-rails.
Please just use object.#{new_method} instead.
WARNING
end
def verify_rendered # :nodoc:
expect_render_mock_proxy.rspec_verify
render_proxy.rspec_verify
end
def unregister_verify_after_each #:nodoc:
@ -65,7 +43,32 @@ module Spec
Spec::Example::ExampleGroup.remove_after(:each, &proc)
end
protected
def should_receive(*args)
if args[0] == :render
register_verify_after_each
render_proxy.should_receive(:render, :expected_from => caller(1)[0])
else
super
end
end
def should_not_receive(*args)
if args[0] == :render
register_verify_after_each
render_proxy.should_not_receive(:render)
else
super
end
end
def stub!(*args)
if args[0] == :render
register_verify_after_each
render_proxy.stub!(:render, :expected_from => caller(1)[0])
else
super
end
end
def verify_rendered_proc #:nodoc:
template = self
@ -80,8 +83,8 @@ module Spec
Spec::Example::ExampleGroup.after(:each, &proc)
end
def expect_render_mock_proxy #:nodoc:
@expect_render_mock_proxy ||= Spec::Mocks::Mock.new("expect_render_mock_proxy")
def render_proxy #:nodoc:
@render_proxy ||= Spec::Mocks::Mock.new("render_proxy")
end
end

View file

@ -32,7 +32,7 @@ module Spec
ensure_that_base_view_path_is_not_set_across_example_groups
end
def initialize(defined_description, &implementation) #:nodoc:
def initialize(defined_description, options={}, &implementation) #:nodoc:
super
@controller_class_name = "Spec::Rails::Example::ViewExampleGroupController"
end
@ -150,7 +150,9 @@ module Spec
protected
def _assigns_hash_proxy
@_assigns_hash_proxy ||= AssignsHashProxy.new @controller
@_assigns_hash_proxy ||= AssignsHashProxy.new self do
@response.template
end
end
end
@ -172,6 +174,9 @@ module Spec
include helper_module
end
end
def forget_variables_added_to_assigns
end
end
end
end

View file

@ -10,15 +10,19 @@ module ActionView #:nodoc:
end
end
end
super(partial_path, local_assigns, deprecated_local_assigns)
begin
super(partial_path, local_assigns, deprecated_local_assigns)
rescue ArgumentError # edge rails > 2.1 changed render_partial to accept only one arg
super(partial_path)
end
end
alias_method :orig_render, :render
def render(options = {}, old_local_assigns = {}, &block)
if expect_render_mock_proxy.send(:__mock_proxy).send(:find_matching_expectation, :render, options)
expect_render_mock_proxy.render(options)
if render_proxy.send(:__mock_proxy).send(:find_matching_expectation, :render, options)
render_proxy.render(options)
else
unless expect_render_mock_proxy.send(:__mock_proxy).send(:find_matching_method_stub, :render, options)
unless render_proxy.send(:__mock_proxy).send(:find_matching_method_stub, :render, options)
orig_render(options, old_local_assigns, &block)
end
end

View file

@ -0,0 +1,14 @@
module Test
module Unit
class TestCase
# Edge rails (r8664) introduces class-wide setup & teardown callbacks for Test::Unit::TestCase.
# Make sure these still get run when running TestCases under rspec:
prepend_before(:each) do
run_callbacks :setup if respond_to?(:run_callbacks)
end
append_after(:each) do
run_callbacks :teardown if respond_to?(:run_callbacks)
end
end
end
end

View file

@ -1,5 +1,6 @@
dir = File.dirname(__FILE__)
require 'spec/rails/matchers/assert_select'
require 'spec/rails/matchers/change'
require 'spec/rails/matchers/have_text'
require 'spec/rails/matchers/include_text'
require 'spec/rails/matchers/redirect_to'

View file

@ -16,6 +16,7 @@ module Spec # :nodoc:
def matches?(response_or_text, &block)
if ActionController::TestResponse === response_or_text and
response_or_text.headers.key?('Content-Type') and
!response_or_text.headers['Content-Type'].blank? and
response_or_text.headers['Content-Type'].to_sym == :xml
@args.unshift(HTML::Document.new(response_or_text.body, false, true).root)
elsif String === response_or_text

View file

@ -0,0 +1,11 @@
module Spec
module Matchers
class Change
def evaluate_value_proc_with_ensured_evaluation_of_proxy
value = evaluate_value_proc_without_ensured_evaluation_of_proxy
ActiveRecord::Associations::AssociationProxy === value ? value.dup : value
end
alias_method_chain :evaluate_value_proc, :ensured_evaluation_of_proxy
end
end
end

View file

@ -10,10 +10,18 @@ module Spec
end
def matches?(response)
@actual = response.rendered_file
full_path(@actual) == full_path(@expected)
if response.respond_to?(:rendered_file)
@actual = response.rendered_file
else
@actual = response.rendered_template.to_s
end
return false if @actual.blank?
given_controller_path, given_file = path_and_file(@actual)
expected_controller_path, expected_file = path_and_file(@expected)
given_controller_path == expected_controller_path && given_file.match(expected_file)
end
def failure_message
"expected #{@expected.inspect}, got #{@actual.inspect}"
end
@ -27,9 +35,21 @@ module Spec
end
private
def full_path(path)
return nil if path.nil?
path.include?('/') ? path : "#{@controller.class.to_s.underscore.gsub('_controller','')}/#{path}"
def path_and_file(path)
parts = path.split('/')
file = parts.pop
controller = parts.empty? ? current_controller_path : parts.join('/')
return controller, file
end
def controller_path_from(path)
parts = path.split('/')
parts.pop
parts.join('/')
end
def current_controller_path
@controller.class.to_s.underscore.gsub(/_controller$/,'')
end
end

View file

@ -9,15 +9,21 @@ module Spec
# methods stubbed out. Additional methods may be easily stubbed (via
# add_stubs) if +stubs+ is passed.
def mock_model(model_class, options_and_stubs = {})
id = next_id
options_and_stubs.reverse_merge!({
id = options_and_stubs[:id] || next_id
options_and_stubs = options_and_stubs.reverse_merge({
:id => id,
:to_param => id.to_s,
:new_record? => false,
:errors => stub("errors", :count => 0)
})
m = mock("#{model_class.name}_#{options_and_stubs[:id]}", options_and_stubs)
m = mock("#{model_class.name}_#{id}", options_and_stubs)
m.send(:__mock_proxy).instance_eval <<-CODE
def @target.as_new_record
self.stub!(:id).and_return nil
self.stub!(:to_param).and_return nil
self.stub!(:new_record?).and_return true
self
end
def @target.is_a?(other)
#{model_class}.ancestors.include?(other)
end
@ -52,24 +58,33 @@ module Spec
# stub_model(Model)
# stub_model(Model).as_new_record
# stub_model(Model, hash_of_stubs)
# stub_model(Model, instance_variable_name, hash_of_stubs)
#
# Creates an instance of +Model+ that is prohibited from accessing the
# database. For each key in +hash_of_stubs+, if the model has a
# matching attribute (determined by asking it, which it answers based
# on schema.rb) are simply assigned the submitted values. If the model
# does not have a matching attribute, the key/value pair is assigned
# as a stub return value using RSpec's mocking/stubbing framework.
# database*. For each key in +hash_of_stubs+, if the model has a
# matching attribute (determined by asking it) are simply assigned the
# submitted values. If the model does not have a matching attribute, the
# key/value pair is assigned as a stub return value using RSpec's
# mocking/stubbing framework.
#
# new_record? is overridden to return the result of id.nil? This means
# that by default new_record? will return false. If you want the
# object to behave as a new record, sending it +as_new_record+ will
# <tt>new_record?</tt> is overridden to return the result of id.nil?
# This means that by default new_record? will return false. If you want
# the object to behave as a new record, sending it +as_new_record+ will
# set the id to nil. You can also explicitly set :id => nil, in which
# case new_record? will return true, but using +as_new_record+ makes
# the example a bit more descriptive.
# case new_record? will return true, but using +as_new_record+ makes the
# example a bit more descriptive.
#
# While you can use stub_model in any example (model, view,
# controller, helper), it is especially useful in view examples,
# which are inherently more state-based than interaction-based.
# While you can use stub_model in any example (model, view, controller,
# helper), it is especially useful in view examples, which are
# inherently more state-based than interaction-based.
#
# == Database Independence
#
# +stub_model+ does not make your examples entirely
# database-independent. It does not stop the model class itself from
# loading up its columns from the database. It just prevents data access
# from the object itself. To completely decouple from the database, take
# a look at libraries like unit_record or NullDB.
#
# == Examples
#
@ -77,9 +92,9 @@ module Spec
# stub_model(Person).as_new_record
# stub_model(Person, :id => 37)
# stub_model(Person) do |person|
# model.first_name = "David"
# person.first_name = "David"
# end
def stub_model(model_class, stubs = {})
def stub_model(model_class, stubs={})
stubs = {:id => next_id}.merge(stubs)
returning model_class.new do |model|
model.id = stubs.delete(:id)
@ -99,7 +114,7 @@ module Spec
# - object.stub!(:method => return_value, :method2 => return_value2, :etc => etc)
#++
# Stubs methods on +object+ (if +object+ is a symbol or string a new mock
# with that name will be created). +stubs+ is a Hash of <tt>method=>value</tt>
# with that name will be created). +stubs+ is a Hash of +method=>value+
def add_stubs(object, stubs = {}) #:nodoc:
m = [String, Symbol].index(object.class) ? mock(object.to_s) : object
stubs.each {|k,v| m.stub!(k).and_return(v)}
@ -114,4 +129,4 @@ module Spec
end
end
end
end

View file

@ -40,15 +40,23 @@ class ActiveRecordSafetyListener
include Singleton
def scenario_started(*args)
if defined?(ActiveRecord::Base)
ActiveRecord::Base.send :increment_open_transactions unless Rails::VERSION::STRING == "1.1.6"
ActiveRecord::Base.connection.begin_db_transaction
if ActiveRecord::Base.connection.respond_to?(:increment_open_transactions)
ActiveRecord::Base.connection.increment_open_transactions
else
ActiveRecord::Base.send :increment_open_transactions
end
end
ActiveRecord::Base.connection.begin_db_transaction
end
def scenario_succeeded(*args)
if defined?(ActiveRecord::Base)
ActiveRecord::Base.connection.rollback_db_transaction
ActiveRecord::Base.send :decrement_open_transactions unless Rails::VERSION::STRING == "1.1.6"
if ActiveRecord::Base.connection.respond_to?(:decrement_open_transactions)
ActiveRecord::Base.connection.decrement_open_transactions
else
ActiveRecord::Base.send :decrement_open_transactions
end
end
end
alias :scenario_pending :scenario_succeeded

View file

@ -1,23 +1,15 @@
module Spec
module Rails
module VERSION #:nodoc:
BUILD_TIME_UTC = 20080615141040
unless defined? MAJOR
MAJOR = 1
MINOR = 1
TINY = 8
STRING = [MAJOR, MINOR, TINY].join('.')
SUMMARY = "rspec-rails #{STRING}"
end
end
end
end
# Verify that the plugin has the same revision as RSpec
if Spec::Rails::VERSION::BUILD_TIME_UTC != Spec::VERSION::BUILD_TIME_UTC
raise <<-EOF
############################################################################
Your RSpec on Rails plugin is incompatible with your installed RSpec.
RSpec : #{Spec::VERSION::BUILD_TIME_UTC}
RSpec on Rails : #{Spec::Rails::VERSION::BUILD_TIME_UTC}
Make sure your RSpec on Rails plugin is compatible with your RSpec gem.
See http://rspec.rubyforge.org/documentation/rails/install.html for details.
############################################################################
EOF
end
end