From 771f42f076326b8eae3a25724d72ad1e0658f5d7 Mon Sep 17 00:00:00 2001 From: Adam Meehan Date: Mon, 7 Jul 2008 15:16:30 +1000 Subject: [PATCH] added rspec-rails for view method specs --- spec/rspec-rails/MIT-LICENSE | 31 ++++ spec/rspec-rails/assert_select.rb | 130 +++++++++++++++ spec/rspec-rails/functional_example_group.rb | 59 +++++++ spec/rspec-rails/helper_example_group.rb | 164 +++++++++++++++++++ spec/rspec-rails/object.rb | 5 + spec/rspec-rails/rails_example_group.rb | 29 ++++ 6 files changed, 418 insertions(+) create mode 100644 spec/rspec-rails/MIT-LICENSE create mode 100644 spec/rspec-rails/assert_select.rb create mode 100644 spec/rspec-rails/functional_example_group.rb create mode 100644 spec/rspec-rails/helper_example_group.rb create mode 100644 spec/rspec-rails/object.rb create mode 100644 spec/rspec-rails/rails_example_group.rb diff --git a/spec/rspec-rails/MIT-LICENSE b/spec/rspec-rails/MIT-LICENSE new file mode 100644 index 0000000..239d8e7 --- /dev/null +++ b/spec/rspec-rails/MIT-LICENSE @@ -0,0 +1,31 @@ +==================================================================== +== RSpec +Copyright (c) 2005-2007 The RSpec Development Team +==================================================================== +== ARTS +Copyright (c) 2006 Kevin Clark, Jake Howerton +==================================================================== +== ZenTest +Copyright (c) 2001-2006 Ryan Davis, Eric Hodel, Zen Spider Software +==================================================================== +== AssertSelect +Copyright (c) 2006 Assaf Arkin +==================================================================== + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/spec/rspec-rails/assert_select.rb b/spec/rspec-rails/assert_select.rb new file mode 100644 index 0000000..1af3511 --- /dev/null +++ b/spec/rspec-rails/assert_select.rb @@ -0,0 +1,130 @@ +# This is a wrapper of assert_select for rspec. + +module Spec # :nodoc: + module Rails + module Matchers + + class AssertSelect #:nodoc: + + def initialize(assertion, spec_scope, *args, &block) + @assertion = assertion + @spec_scope = spec_scope + @args = args + @block = block + end + + 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'].to_sym == :xml + @args.unshift(HTML::Document.new(response_or_text.body, false, true).root) + elsif String === response_or_text + @args.unshift(HTML::Document.new(response_or_text).root) + end + @block = block if block + begin + @spec_scope.send(@assertion, *@args, &@block) + rescue ::Test::Unit::AssertionFailedError => @error + end + + @error.nil? + end + + def failure_message; @error.message; end + def negative_failure_message; "should not #{description}, but did"; end + + def description + { + :assert_select => "have tag#{format_args(*@args)}", + :assert_select_email => "send email#{format_args(*@args)}", + }[@assertion] + end + + private + + def format_args(*args) + return "" if args.empty? + return "(#{arg_list(*args)})" + end + + def arg_list(*args) + args.collect do |arg| + arg.respond_to?(:description) ? arg.description : arg.inspect + end.join(", ") + end + + end + + # :call-seq: + # response.should have_tag(*args, &block) + # string.should have_tag(*args, &block) + # + # wrapper for assert_select with additional support for using + # css selectors to set expectation on Strings. Use this in + # helper specs, for example, to set expectations on the results + # of helper methods. + # + # == Examples + # + # # in a controller spec + # response.should have_tag("div", "some text") + # + # # in a helper spec (person_address_tag is a method in the helper) + # person_address_tag.should have_tag("input#person_address") + # + # see documentation for assert_select at http://api.rubyonrails.org/ + def have_tag(*args, &block) + AssertSelect.new(:assert_select, self, *args, &block) + end + + # wrapper for a nested assert_select + # + # response.should have_tag("div#form") do + # with_tag("input#person_name[name=?]", "person[name]") + # end + # + # see documentation for assert_select at http://api.rubyonrails.org/ + def with_tag(*args, &block) + should have_tag(*args, &block) + end + + # wrapper for a nested assert_select with false + # + # response.should have_tag("div#1") do + # without_tag("span", "some text that shouldn't be there") + # end + # + # see documentation for assert_select at http://api.rubyonrails.org/ + def without_tag(*args, &block) + should_not have_tag(*args, &block) + end + + # :call-seq: + # response.should have_rjs(*args, &block) + # + # wrapper for assert_select_rjs + # + # see documentation for assert_select_rjs at http://api.rubyonrails.org/ + def have_rjs(*args, &block) + AssertSelect.new(:assert_select_rjs, self, *args, &block) + end + + # :call-seq: + # response.should send_email(*args, &block) + # + # wrapper for assert_select_email + # + # see documentation for assert_select_email at http://api.rubyonrails.org/ + def send_email(*args, &block) + AssertSelect.new(:assert_select_email, self, *args, &block) + end + + # wrapper for assert_select_encoded + # + # see documentation for assert_select_encoded at http://api.rubyonrails.org/ + def with_encoded(*args, &block) + should AssertSelect.new(:assert_select_encoded, self, *args, &block) + end + end + end +end diff --git a/spec/rspec-rails/functional_example_group.rb b/spec/rspec-rails/functional_example_group.rb new file mode 100644 index 0000000..6d375c8 --- /dev/null +++ b/spec/rspec-rails/functional_example_group.rb @@ -0,0 +1,59 @@ +module Spec + module Rails + module Example + class FunctionalExampleGroup < RailsExampleGroup + include ActionController::TestProcess + include ActionController::Assertions + + attr_reader :request, :response + before(:each) do + @controller_class = Object.path2class @controller_class_name + raise "Can't determine controller class for #{@controller_class_name}" if @controller_class.nil? + + @controller = @controller_class.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @response.session = @request.session + end + + def params + request.parameters + end + + def flash + response.flash + end + + def session + response.session + end + + # :call-seq: + # assigns() + # + # Hash of instance variables to values that are made available to + # views. == Examples + # + # #in thing_controller.rb + # def new + # @thing = Thing.new + # end + # + # #in thing_controller_spec + # get 'new' + # assigns[:registration].should == Thing.new + #-- + # NOTE - Even though docs only use assigns[:key] format, this supports + # assigns(:key) in order to avoid breaking old specs. + #++ + def assigns(key = nil) + if key.nil? + _assigns_hash_proxy + else + _assigns_hash_proxy[key] + end + end + end + end + end +end diff --git a/spec/rspec-rails/helper_example_group.rb b/spec/rspec-rails/helper_example_group.rb new file mode 100644 index 0000000..af2f035 --- /dev/null +++ b/spec/rspec-rails/helper_example_group.rb @@ -0,0 +1,164 @@ +module Spec + module Rails + module Example + # Helper Specs live in $RAILS_ROOT/spec/helpers/. + # + # Helper Specs use Spec::Rails::Example::HelperExampleGroup, which allows you to + # include your Helper directly in the context and write specs directly + # against its methods. + # + # HelperExampleGroup also includes the standard lot of ActionView::Helpers in case your + # helpers rely on any of those. + # + # == Example + # + # class ThingHelper + # def number_of_things + # Thing.count + # end + # end + # + # describe "ThingHelper example_group" do + # include ThingHelper + # it "should tell you the number of things" do + # Thing.should_receive(:count).and_return(37) + # number_of_things.should == 37 + # end + # end + class HelperExampleGroup < FunctionalExampleGroup + class HelperObject < ActionView::Base + def protect_against_forgery? + false + end + + def session=(session) + @session = session + end + + def request=(request) + @request = request + end + + def flash=(flash) + @flash = flash + end + + def params=(params) + @params = params + end + + def controller=(controller) + @controller = controller + end + + private + attr_reader :session, :request, :flash, :params, :controller + end + + class << self + # The helper name.... + def helper_name(name=nil) + @helper_being_described = "#{name}_helper".camelize.constantize + send :include, @helper_being_described + end + + def helper + @helper_object ||= returning HelperObject.new do |helper_object| + if @helper_being_described.nil? + if described_type.class == Module + helper_object.extend described_type + end + else + helper_object.extend @helper_being_described + end + end + end + end + + # Returns an instance of ActionView::Base with the helper being spec'd + # included. + # + # == Example + # + # describe PersonHelper do + # it "should write a link to person with the name" do + # assigns[:person] = mock_model(Person, :full_name => "Full Name", :id => 37, :new_record? => false) + # helper.link_to_person.should == %{Full Name} + # end + # end + # + # module PersonHelper + # def link_to_person + # link_to person.full_name, url_for(person) + # end + # end + # + def helper + self.class.helper + end + + # Reverse the load order so that custom helpers which are defined last + # are also loaded last. + ActionView::Base.included_modules.reverse.each do |mod| + include mod if mod.parents.include?(ActionView::Helpers) + end + + before(:all) do + @controller_class_name = 'Spec::Rails::Example::HelperExampleGroupController' + end + + before(:each) do + @controller.request = @request + @controller.url = ActionController::UrlRewriter.new @request, {} # url_for + + @flash = ActionController::Flash::FlashHash.new + session['flash'] = @flash + + ActionView::Helpers::AssetTagHelper::reset_javascript_include_default + + helper.session = session + helper.request = @request + helper.flash = flash + helper.params = params + helper.controller = @controller + end + + def flash + @flash + end + + def eval_erb(text) + erb_args = [text] + if helper.respond_to?(:output_buffer) + erb_args += [nil, nil, '@output_buffer'] + end + + helper.instance_eval do + ERB.new(*erb_args).result(binding) + end + end + + # TODO: BT - Helper Examples should proxy method_missing to a Rails View instance. + # When that is done, remove this method + def protect_against_forgery? + false + end + + Spec::Example::ExampleGroupFactory.register(:helper, self) + + protected + def _assigns_hash_proxy + @_assigns_hash_proxy ||= AssignsHashProxy.new helper + end + + end + + class HelperExampleGroupController < ApplicationController #:nodoc: + attr_accessor :request, :url + + # Re-raise errors + def rescue_action(e); raise e; end + end + end + end +end diff --git a/spec/rspec-rails/object.rb b/spec/rspec-rails/object.rb new file mode 100644 index 0000000..68fce0b --- /dev/null +++ b/spec/rspec-rails/object.rb @@ -0,0 +1,5 @@ +class Object # :nodoc: + def self.path2class(klassname) + klassname.split('::').inject(Object) { |k,n| k.const_get n } + end +end diff --git a/spec/rspec-rails/rails_example_group.rb b/spec/rspec-rails/rails_example_group.rb new file mode 100644 index 0000000..d60037d --- /dev/null +++ b/spec/rspec-rails/rails_example_group.rb @@ -0,0 +1,29 @@ +require 'spec/interop/test' + +if ActionView::Base.respond_to?(:cache_template_extension) + ActionView::Base.cache_template_extensions = false +end + +module Spec + module Rails + + module Example + 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) + end + + include Spec::Rails::Matchers + include Spec::Rails::Mocks + + Spec::Example::ExampleGroupFactory.default(self) + + end + end + end +end