diff --git a/README.md b/README.md index d90a0ea..f14ad3e 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,22 @@ In your `Spork.prefork` block of `spec_helper.rb`, add this: require 'draper/test/rspec_integration' ``` +### Isolated tests + +In tests, Draper needs to build a view context to access helper methods. By default, it will create an `ApplicationController` and then use its view context. If you are speeding up your test suite by testing each component in isolation, you can eliminate this dependency by putting the following in your `spec_helper` or similar: + +```ruby +Draper::ViewContext.test_strategy :fast +``` + +In doing so, your decorators will no longer have access to your application's helpers. If you need to selectively include such helpers, you can pass a block: + +```ruby +Draper::ViewContext.test_strategy :fast do + include ApplicationHelper +end +``` + ## Advanced usage ### Shared Decorator Methods diff --git a/Rakefile b/Rakefile index 678c261..848fc54 100644 --- a/Rakefile +++ b/Rakefile @@ -60,6 +60,7 @@ end namespace "db" do desc "Set up databases for integration testing" task "setup" do + puts "Setting up databases" run_in_dummy_app "rm -f db/*.sqlite3" run_in_dummy_app "RAILS_ENV=development rake db:schema:load db:seed" run_in_dummy_app "RAILS_ENV=production rake db:schema:load db:seed" diff --git a/lib/draper.rb b/lib/draper.rb index f071f37..52b146a 100644 --- a/lib/draper.rb +++ b/lib/draper.rb @@ -24,10 +24,11 @@ module Draper base.class_eval do include Draper::ViewContext extend Draper::HelperSupport - before_filter ->(controller) { - Draper::ViewContext.current = nil - Draper::ViewContext.current_controller = controller - } + + before_filter do |controller| + Draper::ViewContext.clear! + Draper::ViewContext.controller = controller + end end end diff --git a/lib/draper/test/rspec_integration.rb b/lib/draper/test/rspec_integration.rb index c41c11d..0a787fe 100755 --- a/lib/draper/test/rspec_integration.rb +++ b/lib/draper/test/rspec_integration.rb @@ -8,5 +8,9 @@ module Draper RSpec.configure do |config| config.include DecoratorExampleGroup, example_group: {file_path: %r{spec/decorators}}, type: :decorator + + [:decorator, :controller, :mailer].each do |type| + config.after(:each, type: type) { Draper::ViewContext.clear! } + end end end diff --git a/lib/draper/test_case.rb b/lib/draper/test_case.rb index eb625cd..62320e9 100644 --- a/lib/draper/test_case.rb +++ b/lib/draper/test_case.rb @@ -13,6 +13,13 @@ module Draper end class TestCase < active_support_test_case + module ViewContextTeardown + def teardown + super + Draper::ViewContext.clear! + end + end + module Behavior if defined?(::Devise) require 'draper/test/devise_helper' @@ -29,5 +36,18 @@ module Draper end include Behavior + include ViewContextTeardown + end +end + +if defined?(ActionController::TestCase) + class ActionController::TestCase + include Draper::TestCase::ViewContextTeardown + end +end + +if defined?(ActionMailer::TestCase) + class ActionMailer::TestCase + include Draper::TestCase::ViewContextTeardown end end diff --git a/lib/draper/view_context.rb b/lib/draper/view_context.rb index d545d59..633211e 100755 --- a/lib/draper/view_context.rb +++ b/lib/draper/view_context.rb @@ -1,37 +1,89 @@ +require 'draper/view_context/build_strategy' require 'request_store' module Draper module ViewContext + # Hooks into a controller or mailer to save the view context in {current}. def view_context super.tap do |context| Draper::ViewContext.current = context end end - def self.current_controller - RequestStore.store[:current_controller] || ApplicationController.new + # Returns the current controller. + def self.controller + RequestStore.store[:current_controller] end - def self.current_controller=(controller) + # Sets the current controller. + def self.controller=(controller) RequestStore.store[:current_controller] = controller end + # Returns the current view context, or builds one if none is saved. def self.current - RequestStore.store[:current_view_context] ||= build_view_context + RequestStore.store[:current_view_context] ||= build end - def self.current=(context) - RequestStore.store[:current_view_context] = context + # Sets the current view context. + def self.current=(view_context) + RequestStore.store[:current_view_context] = view_context end + # Clears the saved controller and view context. + def self.clear! + self.controller = nil + self.current = nil + end + + # Builds a new view context for usage in tests. See {test_strategy} for + # details of how the view context is built. + def self.build + build_strategy.call + end + + # Configures the strategy used to build view contexts in tests, which + # defaults to `:full` if `test_strategy` has not been called. Evaluates + # the block, if given, in the context of the view context's class. + # + # @example Pass a block to add helper methods to the view context: + # Draper::ViewContext.test_strategy :fast do + # include ApplicationHelper + # end + # + # @param [:full, :fast] name + # the strategy to use: + # + # `:full` - build a fully-working view context. Your Rails environment + # must be loaded, including your `ApplicationController`. + # + # `:fast` - build a minimal view context in tests, with no dependencies + # on other components of your application. + def self.test_strategy(name, &block) + @build_strategy = Draper::ViewContext::BuildStrategy.new(name, &block) + end + + # @private + def self.build_strategy + @build_strategy ||= Draper::ViewContext::BuildStrategy.new(:full) + end + + # @deprecated Use {controller} instead. + def self.current_controller + ActiveSupport::Deprecation.warn("Draper::ViewContext.current_controller is deprecated (use controller instead)", caller) + self.controller || ApplicationController.new + end + + # @deprecated Use {controller=} instead. + def self.current_controller=(controller) + ActiveSupport::Deprecation.warn("Draper::ViewContext.current_controller= is deprecated (use controller instead)", caller) + self.controller = controller + end + + # @deprecated Use {build} instead. def self.build_view_context - current_controller.view_context.tap do |context| - if defined?(ActionController::TestRequest) - context.controller.request ||= ActionController::TestRequest.new - context.request ||= context.controller.request - context.params ||= {} - end - end + ActiveSupport::Deprecation.warn("Draper::ViewContext.build_view_context is deprecated (use build instead)", caller) + build end end end diff --git a/lib/draper/view_context/build_strategy.rb b/lib/draper/view_context/build_strategy.rb new file mode 100644 index 0000000..3d4dc11 --- /dev/null +++ b/lib/draper/view_context/build_strategy.rb @@ -0,0 +1,48 @@ +module Draper + module ViewContext + # @private + module BuildStrategy + + def self.new(name, &block) + const_get(name.to_s.camelize).new(&block) + end + + class Fast + def initialize(&block) + @view_context_class = Class.new(ActionView::Base, &block) + end + + def call + view_context_class.new + end + + private + + attr_reader :view_context_class + end + + class Full + def initialize(&block) + @block = block + end + + def call + controller.view_context.tap do |context| + context.singleton_class.class_eval(&block) if block + end + end + + private + + attr_reader :block + + def controller + (Draper::ViewContext.controller || ApplicationController.new).tap do |controller| + controller.request ||= ActionController::TestRequest.new if defined?(ActionController::TestRequest) + end + end + end + + end + end +end diff --git a/spec/draper/decorator_spec.rb b/spec/draper/decorator_spec.rb index 00278b1..94cc635 100755 --- a/spec/draper/decorator_spec.rb +++ b/spec/draper/decorator_spec.rb @@ -444,7 +444,7 @@ module Draper it "does not confuse Kernel#Array" do decorator = Decorator.new(Model.new) - expect(Array(decorator)).to be_a Array + expect(Array(decorator)).to be_an Array end it "delegates already-delegated methods" do diff --git a/spec/draper/view_context/build_strategy_spec.rb b/spec/draper/view_context/build_strategy_spec.rb new file mode 100644 index 0000000..abb154c --- /dev/null +++ b/spec/draper/view_context/build_strategy_spec.rb @@ -0,0 +1,116 @@ +require 'spec_helper' + +def fake_view_context + double("ViewContext") +end + +def fake_controller(view_context = fake_view_context) + double("Controller", view_context: view_context, request: double("Request")) +end + +module Draper + describe ViewContext::BuildStrategy::Full do + describe "#call" do + context "when a current controller is set" do + it "returns the controller's view context" do + view_context = fake_view_context + ViewContext.stub controller: fake_controller(view_context) + strategy = ViewContext::BuildStrategy::Full.new + + expect(strategy.call).to be view_context + end + end + + context "when a current controller is not set" do + it "uses ApplicationController" do + view_context = fake_view_context + stub_const "ApplicationController", double(new: fake_controller(view_context)) + strategy = ViewContext::BuildStrategy::Full.new + + expect(strategy.call).to be view_context + end + end + + it "adds a request if one is not defined" do + controller = Class.new(ActionController::Base).new + ViewContext.stub controller: controller + strategy = ViewContext::BuildStrategy::Full.new + + expect(controller.request).to be_nil + strategy.call + expect(controller.request).to be_an ActionController::TestRequest + expect(controller.params).to eq({}) + + # sanity checks + expect(controller.view_context.request).to be controller.request + expect(controller.view_context.params).to be controller.params + end + + it "adds methods to the view context from the constructor block" do + ViewContext.stub controller: fake_controller + strategy = ViewContext::BuildStrategy::Full.new do + def a_helper_method; end + end + + expect(strategy.call).to respond_to :a_helper_method + end + + it "includes modules into the view context from the constructor block" do + view_context = Object.new + ViewContext.stub controller: fake_controller(view_context) + helpers = Module.new do + def a_helper_method; end + end + strategy = ViewContext::BuildStrategy::Full.new do + include helpers + end + + expect(strategy.call).to respond_to :a_helper_method + end + end + end + + describe ViewContext::BuildStrategy::Fast do + describe "#call" do + it "returns an instance of a subclass of ActionView::Base" do + strategy = ViewContext::BuildStrategy::Fast.new + + returned = strategy.call + + expect(returned).to be_an ActionView::Base + expect(returned.class).not_to be ActionView::Base + end + + it "returns different instances each time" do + strategy = ViewContext::BuildStrategy::Fast.new + + expect(strategy.call).not_to be strategy.call + end + + it "returns the same subclass each time" do + strategy = ViewContext::BuildStrategy::Fast.new + + expect(strategy.call.class).to be strategy.call.class + end + + it "adds methods to the view context from the constructor block" do + strategy = ViewContext::BuildStrategy::Fast.new do + def a_helper_method; end + end + + expect(strategy.call).to respond_to :a_helper_method + end + + it "includes modules into the view context from the constructor block" do + helpers = Module.new do + def a_helper_method; end + end + strategy = ViewContext::BuildStrategy::Fast.new do + include helpers + end + + expect(strategy.call).to respond_to :a_helper_method + end + end + end +end diff --git a/spec/draper/view_context_spec.rb b/spec/draper/view_context_spec.rb new file mode 100644 index 0000000..75da419 --- /dev/null +++ b/spec/draper/view_context_spec.rb @@ -0,0 +1,117 @@ +require 'spec_helper' + +module Draper + describe ViewContext do + describe "#view_context" do + let(:base) { Class.new { def view_context; :controller_view_context; end } } + let(:controller) { Class.new(base) { include ViewContext } } + + it "saves the superclass's view context" do + controller.new.view_context + expect(ViewContext.current).to be :controller_view_context + end + + it "returns the superclass's view context" do + expect(controller.new.view_context).to be :controller_view_context + end + end + + describe ".controller" do + it "returns the stored controller from RequestStore" do + RequestStore.stub store: {current_controller: :stored_controller} + expect(ViewContext.controller).to be :stored_controller + end + end + + describe ".controller=" do + it "stores a controller in RequestStore" do + store = {} + RequestStore.stub store: store + + ViewContext.controller = :stored_controller + expect(store[:current_controller]).to be :stored_controller + end + end + + describe ".current" do + it "returns the stored view context from RequestStore" do + RequestStore.stub store: {current_view_context: :stored_view_context} + expect(ViewContext.current).to be :stored_view_context + end + + it "falls back to building a view context" do + RequestStore.stub store: {} + ViewContext.should_receive(:build).and_return(:new_view_context) + expect(ViewContext.current).to be :new_view_context + end + end + + describe ".current=" do + it "stores a view context in RequestStore" do + store = {} + RequestStore.stub store: store + + ViewContext.current = :stored_view_context + expect(store[:current_view_context]).to be :stored_view_context + end + end + + describe ".clear!" do + it "clears the stored controller and view controller" do + store = {current_controller: :stored_controller, current_view_context: :stored_view_context} + RequestStore.stub store: store + + ViewContext.clear! + expect(store[:current_controller]).to be_nil + expect(store[:current_view_context]).to be_nil + end + end + + describe ".build" do + it "calls the build strategy" do + ViewContext.stub build_strategy: ->{ :new_view_context } + expect(ViewContext.build).to be :new_view_context + end + end + + describe ".build_strategy" do + it "defaults to full" do + expect(ViewContext.build_strategy).to be_a ViewContext::BuildStrategy::Full + end + + it "memoizes" do + expect(ViewContext.build_strategy).to be ViewContext.build_strategy + end + end + + describe ".test_strategy" do + protect_module ViewContext + + context "with :fast" do + it "creates a fast strategy" do + ViewContext.test_strategy :fast + expect(ViewContext.build_strategy).to be_a ViewContext::BuildStrategy::Fast + end + + it "passes a block to the strategy" do + ViewContext::BuildStrategy::Fast.stub(:new).and_return{|&block| block.call} + + expect(ViewContext.test_strategy(:fast){:passed}).to be :passed + end + end + + context "with :full" do + it "creates a full strategy" do + ViewContext.test_strategy :full + expect(ViewContext.build_strategy).to be_a ViewContext::BuildStrategy::Full + end + + it "passes a block to the strategy" do + ViewContext::BuildStrategy::Full.stub(:new).and_return{|&block| block.call} + + expect(ViewContext.test_strategy(:full){:passed}).to be :passed + end + end + end + end +end diff --git a/spec/dummy/README.rdoc b/spec/dummy/README.rdoc deleted file mode 100644 index 7c36f23..0000000 --- a/spec/dummy/README.rdoc +++ /dev/null @@ -1,261 +0,0 @@ -== Welcome to Rails - -Rails is a web-application framework that includes everything needed to create -database-backed web applications according to the Model-View-Control pattern. - -This pattern splits the view (also called the presentation) into "dumb" -templates that are primarily responsible for inserting pre-built data in between -HTML tags. The model contains the "smart" domain objects (such as Account, -Product, Person, Post) that holds all the business logic and knows how to -persist themselves to a database. The controller handles the incoming requests -(such as Save New Account, Update Product, Show Post) by manipulating the model -and directing data to the view. - -In Rails, the model is handled by what's called an object-relational mapping -layer entitled Active Record. This layer allows you to present the data from -database rows as objects and embellish these data objects with business logic -methods. You can read more about Active Record in -link:files/vendor/rails/activerecord/README.html. - -The controller and view are handled by the Action Pack, which handles both -layers by its two parts: Action View and Action Controller. These two layers -are bundled in a single package due to their heavy interdependence. This is -unlike the relationship between the Active Record and Action Pack that is much -more separate. Each of these packages can be used independently outside of -Rails. You can read more about Action Pack in -link:files/vendor/rails/actionpack/README.html. - - -== Getting Started - -1. At the command prompt, create a new Rails application: - rails new myapp (where myapp is the application name) - -2. Change directory to myapp and start the web server: - cd myapp; rails server (run with --help for options) - -3. Go to http://localhost:3000/ and you'll see: - "Welcome aboard: You're riding Ruby on Rails!" - -4. Follow the guidelines to start developing your application. You can find -the following resources handy: - -* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html -* Ruby on Rails Tutorial Book: http://www.railstutorial.org/ - - -== Debugging Rails - -Sometimes your application goes wrong. Fortunately there are a lot of tools that -will help you debug it and get it back on the rails. - -First area to check is the application log files. Have "tail -f" commands -running on the server.log and development.log. Rails will automatically display -debugging and runtime information to these files. Debugging info will also be -shown in the browser on requests from 127.0.0.1. - -You can also log your own messages directly into the log file from your code -using the Ruby logger class from inside your controllers. Example: - - class WeblogController < ActionController::Base - def destroy - @weblog = Weblog.find(params[:id]) - @weblog.destroy - logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") - end - end - -The result will be a message in your log file along the lines of: - - Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1! - -More information on how to use the logger is at http://www.ruby-doc.org/core/ - -Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are -several books available online as well: - -* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe) -* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) - -These two books will bring you up to speed on the Ruby language and also on -programming in general. - - -== Debugger - -Debugger support is available through the debugger command when you start your -Mongrel or WEBrick server with --debugger. This means that you can break out of -execution at any point in the code, investigate and change the model, and then, -resume execution! You need to install ruby-debug to run the server in debugging -mode. With gems, use sudo gem install ruby-debug. Example: - - class WeblogController < ActionController::Base - def index - @posts = Post.all - debugger - end - end - -So the controller will accept the action, run the first line, then present you -with a IRB prompt in the server window. Here you can do things like: - - >> @posts.inspect - => "[#nil, "body"=>nil, "id"=>"1"}>, - #"Rails", "body"=>"Only ten..", "id"=>"2"}>]" - >> @posts.first.title = "hello from a debugger" - => "hello from a debugger" - -...and even better, you can examine how your runtime objects actually work: - - >> f = @posts.first - => #nil, "body"=>nil, "id"=>"1"}> - >> f. - Display all 152 possibilities? (y or n) - -Finally, when you're ready to resume execution, you can enter "cont". - - -== Console - -The console is a Ruby shell, which allows you to interact with your -application's domain model. Here you'll have all parts of the application -configured, just like it is when the application is running. You can inspect -domain models, change values, and save to the database. Starting the script -without arguments will launch it in the development environment. - -To start the console, run rails console from the application -directory. - -Options: - -* Passing the -s, --sandbox argument will rollback any modifications - made to the database. -* Passing an environment name as an argument will load the corresponding - environment. Example: rails console production. - -To reload your controllers and models after launching the console run -reload! - -More information about irb can be found at: -link:http://www.rubycentral.org/pickaxe/irb.html - - -== dbconsole - -You can go to the command line of your database directly through rails -dbconsole. You would be connected to the database with the credentials -defined in database.yml. Starting the script without arguments will connect you -to the development database. Passing an argument will connect you to a different -database, like rails dbconsole production. Currently works for MySQL, -PostgreSQL and SQLite 3. - -== Description of Contents - -The default directory structure of a generated Ruby on Rails application: - - |-- app - | |-- assets - | |-- images - | |-- javascripts - | `-- stylesheets - | |-- controllers - | |-- helpers - | |-- mailers - | |-- models - | `-- views - | `-- layouts - |-- config - | |-- environments - | |-- initializers - | `-- locales - |-- db - |-- doc - |-- lib - | `-- tasks - |-- log - |-- public - |-- script - |-- test - | |-- fixtures - | |-- functional - | |-- integration - | |-- performance - | `-- unit - |-- tmp - | |-- cache - | |-- pids - | |-- sessions - | `-- sockets - `-- vendor - |-- assets - `-- stylesheets - `-- plugins - -app - Holds all the code that's specific to this particular application. - -app/assets - Contains subdirectories for images, stylesheets, and JavaScript files. - -app/controllers - Holds controllers that should be named like weblogs_controller.rb for - automated URL mapping. All controllers should descend from - ApplicationController which itself descends from ActionController::Base. - -app/models - Holds models that should be named like post.rb. Models descend from - ActiveRecord::Base by default. - -app/views - Holds the template files for the view that should be named like - weblogs/index.html.erb for the WeblogsController#index action. All views use - eRuby syntax by default. - -app/views/layouts - Holds the template files for layouts to be used with views. This models the - common header/footer method of wrapping views. In your views, define a layout - using the layout :default and create a file named default.html.erb. - Inside default.html.erb, call <% yield %> to render the view using this - layout. - -app/helpers - Holds view helpers that should be named like weblogs_helper.rb. These are - generated for you automatically when using generators for controllers. - Helpers can be used to wrap functionality for your views into methods. - -config - Configuration files for the Rails environment, the routing map, the database, - and other dependencies. - -db - Contains the database schema in schema.rb. db/migrate contains all the - sequence of Migrations for your schema. - -doc - This directory is where your application documentation will be stored when - generated using rake doc:app - -lib - Application specific libraries. Basically, any kind of custom code that - doesn't belong under controllers, models, or helpers. This directory is in - the load path. - -public - The directory available for the web server. Also contains the dispatchers and the - default HTML files. This should be set as the DOCUMENT_ROOT of your web - server. - -script - Helper scripts for automation and generation. - -test - Unit and functional tests along with fixtures. When using the rails generate - command, template test files will be generated for you and placed in this - directory. - -vendor - External libraries that the application depends on. Also includes the plugins - subdirectory. If the app has frozen rails, those gems also go here, under - vendor/rails/. This directory is in the load path. diff --git a/spec/dummy/app/views/posts/_post.html.erb b/spec/dummy/app/views/posts/_post.html.erb index 35f25f7..8170f7f 100644 --- a/spec/dummy/app/views/posts/_post.html.erb +++ b/spec/dummy/app/views/posts/_post.html.erb @@ -2,6 +2,9 @@
Environment:
<%= Rails.env %>
+
Draper view context controller:
+
<%= Draper::ViewContext.current.controller.class %>
+
Posted:
<%= post.posted_date %>
diff --git a/spec/dummy/config/application.rb b/spec/dummy/config/application.rb index 9d218ba..a29ab65 100644 --- a/spec/dummy/config/application.rb +++ b/spec/dummy/config/application.rb @@ -62,3 +62,4 @@ module Dummy end end +ActiveRecord::Migration.verbose = false diff --git a/spec/dummy/config/environments/development.rb b/spec/dummy/config/environments/development.rb index d707335..23294c6 100644 --- a/spec/dummy/config/environments/development.rb +++ b/spec/dummy/config/environments/development.rb @@ -6,9 +6,6 @@ Dummy::Application.configure do # since you don't have to restart the web server when you make code changes. config.cache_classes = false - # Log error messages when you accidentally call methods on nil. - config.whiny_nils = true - # Show full error reports and disable caching config.consider_all_requests_local = true config.action_controller.perform_caching = false @@ -19,6 +16,8 @@ Dummy::Application.configure do # Only use best-standards-support built into browsers config.action_dispatch.best_standards_support = :builtin + config.eager_load = false + # Raise exception on mass assignment protection for Active Record models # config.active_record.mass_assignment_sanitizer = :strict diff --git a/spec/dummy/config/environments/production.rb b/spec/dummy/config/environments/production.rb index 238a57d..851d8e7 100644 --- a/spec/dummy/config/environments/production.rb +++ b/spec/dummy/config/environments/production.rb @@ -11,6 +11,8 @@ Dummy::Application.configure do # Disable Rails's static asset server (Apache or nginx will already do this) config.serve_static_assets = false + config.eager_load = true + # Defaults to nil and saved in location specified by config.assets.prefix # config.assets.manifest = YOUR_PATH diff --git a/spec/dummy/config/environments/test.rb b/spec/dummy/config/environments/test.rb index 968336c..58a8b6b 100644 --- a/spec/dummy/config/environments/test.rb +++ b/spec/dummy/config/environments/test.rb @@ -9,10 +9,7 @@ Dummy::Application.configure do # Configure static asset server for tests with Cache-Control for performance # config.serve_static_assets = true - config.static_cache_control = "public, max-age=3600" - - # Log error messages when you accidentally call methods on nil - config.whiny_nils = true + # config.static_cache_control = "public, max-age=3600" # Show full error reports and disable caching config.consider_all_requests_local = true @@ -29,4 +26,6 @@ Dummy::Application.configure do # Print deprecation notices to the stderr config.active_support.deprecation = :stderr + + config.eager_load = false end diff --git a/spec/dummy/config/initializers/secret_token.rb b/spec/dummy/config/initializers/secret_token.rb index f9973ba..cc38653 100644 --- a/spec/dummy/config/initializers/secret_token.rb +++ b/spec/dummy/config/initializers/secret_token.rb @@ -4,4 +4,5 @@ # If you change this key, all old signed cookies will become invalid! # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. +Dummy::Application.config.secret_key_base = 'c2e3474d3816f60bf6dd0f3b983e7283c7ff5373e11a96935340b544a31964dbe5ee077136165ee2975e0005f5e80207c0059e6d5589699031242ba5a06dcb87' Dummy::Application.config.secret_token = 'c2e3474d3816f60bf6dd0f3b983e7283c7ff5373e11a96935340b544a31964dbe5ee077136165ee2975e0005f5e80207c0059e6d5589699031242ba5a06dcb87' diff --git a/spec/dummy/fast_spec/post_decorator_spec.rb b/spec/dummy/fast_spec/post_decorator_spec.rb new file mode 100644 index 0000000..a38a2d1 --- /dev/null +++ b/spec/dummy/fast_spec/post_decorator_spec.rb @@ -0,0 +1,38 @@ +require 'draper' +require 'rspec' + +require 'active_model/naming' +require_relative '../app/decorators/post_decorator' + +Draper::ViewContext.test_strategy :fast + +Post = Struct.new(:id) { extend ActiveModel::Naming } + +describe PostDecorator do + let(:decorator) { PostDecorator.new(source) } + let(:source) { Post.new(42) } + + it "can use built-in helpers" do + expect(decorator.truncated).to eq "Once upon a..." + end + + it "can use built-in private helpers" do + expect(decorator.html_escaped).to eq "<script>danger</script>" + end + + it "can't use user-defined helpers from app/helpers" do + expect{decorator.hello_world}.to raise_error NoMethodError, /hello_world/ + end + + it "can't use path helpers" do + expect{decorator.path_with_model}.to raise_error NoMethodError, /post_path/ + end + + it "can't use url helpers" do + expect{decorator.url_with_model}.to raise_error NoMethodError, /post_url/ + end + + it "can't be passed implicitly to url_for" do + expect{decorator.link}.to raise_error + end +end diff --git a/spec/dummy/lib/tasks/test.rake b/spec/dummy/lib/tasks/test.rake index 8e2a9d4..56052a8 100644 --- a/spec/dummy/lib/tasks/test.rake +++ b/spec/dummy/lib/tasks/test.rake @@ -1,10 +1,16 @@ -require 'rspec/core/rake_task' require 'rake/testtask' +require 'rspec/core/rake_task' -RSpec::Core::RakeTask.new :rspec - -Rake::TestTask.new :mini_test do |t| - t.test_files = ["mini_test/mini_test_integration_test.rb"] +Rake::Task[:test].clear +Rake::TestTask.new :test do |t| + t.libs << "test" + t.pattern = "test/**/*_test.rb" end -task :default => [:rspec, :mini_test] +RSpec::Core::RakeTask.new :spec + +RSpec::Core::RakeTask.new :fast_spec do |t| + t.pattern = "fast_spec/**/*_spec.rb" +end + +task :default => [:test, :spec, :fast_spec] diff --git a/spec/dummy/spec/decorators/rspec_integration_spec.rb b/spec/dummy/spec/decorators/helpers_spec.rb similarity index 74% rename from spec/dummy/spec/decorators/rspec_integration_spec.rb rename to spec/dummy/spec/decorators/helpers_spec.rb index 716b07e..2376626 100644 --- a/spec/dummy/spec/decorators/rspec_integration_spec.rb +++ b/spec/dummy/spec/decorators/helpers_spec.rb @@ -1,8 +1,4 @@ -describe "A spec in this folder" do - it "is a decorator spec" do - expect(example.metadata[:type]).to be :decorator - end -end +require 'spec_helper' describe "A decorator spec" do it "can access helpers through `helper`" do diff --git a/spec/dummy/spec/decorators/post_decorator_spec.rb b/spec/dummy/spec/decorators/post_decorator_spec.rb index 7ecda5d..7f1b738 100755 --- a/spec/dummy/spec/decorators/post_decorator_spec.rb +++ b/spec/dummy/spec/decorators/post_decorator_spec.rb @@ -39,4 +39,8 @@ describe PostDecorator do it "serializes overriden attributes" do expect(decorator.serializable_hash["updated_at"]).to be :overridden end + + it "uses a test view context from ApplicationController" do + expect(Draper::ViewContext.current.controller).to be_an ApplicationController + end end diff --git a/spec/dummy/spec/decorators/spec_type_spec.rb b/spec/dummy/spec/decorators/spec_type_spec.rb new file mode 100644 index 0000000..7a98ce7 --- /dev/null +++ b/spec/dummy/spec/decorators/spec_type_spec.rb @@ -0,0 +1,7 @@ +require 'spec_helper' + +describe "A spec in this folder" do + it "is a decorator spec" do + expect(example.metadata[:type]).to be :decorator + end +end diff --git a/spec/dummy/spec/decorators/view_context_spec.rb b/spec/dummy/spec/decorators/view_context_spec.rb new file mode 100644 index 0000000..2ba93a9 --- /dev/null +++ b/spec/dummy/spec/decorators/view_context_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +def it_does_not_leak_view_context + 2.times do + it "has an independent view context" do + expect(Draper::ViewContext.current).not_to be :leaked + Draper::ViewContext.current = :leaked + end + end +end + +describe "A decorator spec", type: :decorator do + it_does_not_leak_view_context +end + +describe "A controller spec", type: :controller do + it_does_not_leak_view_context +end + +describe "A mailer spec", type: :mailer do + it_does_not_leak_view_context +end diff --git a/spec/dummy/spec/mailers/post_mailer_spec.rb b/spec/dummy/spec/mailers/post_mailer_spec.rb index 3297766..3d6699a 100644 --- a/spec/dummy/spec/mailers/post_mailer_spec.rb +++ b/spec/dummy/spec/mailers/post_mailer_spec.rb @@ -25,5 +25,9 @@ describe PostMailer do it "can use url helpers with an id" do expect(email_body).to have_css "#url_with_id", text: "http://www.example.com:12345/en/posts/#{post.id}" end + + it "uses the correct view context controller" do + expect(email_body).to have_css "#controller", text: "PostMailer" + end end end diff --git a/spec/dummy/test/decorators/minitest/helpers_test.rb b/spec/dummy/test/decorators/minitest/helpers_test.rb new file mode 100644 index 0000000..49b0396 --- /dev/null +++ b/spec/dummy/test/decorators/minitest/helpers_test.rb @@ -0,0 +1,15 @@ +require 'minitest_helper' + +describe "A decorator test" do + it "can access helpers through `helper`" do + assert_equal "

Help!

", helper.content_tag(:p, "Help!") + end + + it "can access helpers through `helpers`" do + assert_equal "

Help!

", helpers.content_tag(:p, "Help!") + end + + it "can access helpers through `h`" do + assert_equal "

Help!

", h.content_tag(:p, "Help!") + end +end diff --git a/spec/dummy/mini_test/mini_test_integration_test.rb b/spec/dummy/test/decorators/minitest/spec_type_test.rb similarity index 83% rename from spec/dummy/mini_test/mini_test_integration_test.rb rename to spec/dummy/test/decorators/minitest/spec_type_test.rb index 0a25ba2..b1710a6 100644 --- a/spec/dummy/mini_test/mini_test_integration_test.rb +++ b/spec/dummy/test/decorators/minitest/spec_type_test.rb @@ -1,6 +1,4 @@ -require File.expand_path('../../config/environment', __FILE__) -require 'minitest/autorun' -require 'minitest/rails' +require 'minitest_helper' def it_is_a_decorator_test it "is a decorator test" do @@ -33,6 +31,14 @@ describe "AnyDecorator" do it_is_a_decorator_test end +describe "Any decorator" do + it_is_a_decorator_test +end + +describe "AnyDecoratorTest" do + it_is_a_decorator_test +end + describe "Any decorator test" do it_is_a_decorator_test end diff --git a/spec/dummy/test/decorators/minitest/view_context_test.rb b/spec/dummy/test/decorators/minitest/view_context_test.rb new file mode 100644 index 0000000..c1df670 --- /dev/null +++ b/spec/dummy/test/decorators/minitest/view_context_test.rb @@ -0,0 +1,24 @@ +require 'minitest_helper' + +def it_does_not_leak_view_context + 2.times do + it "has an independent view context" do + refute_equal :leaked, Draper::ViewContext.current + Draper::ViewContext.current = :leaked + end + end +end + +describe "A decorator test" do + it_does_not_leak_view_context +end + +describe "A controller test" do + tests Class.new(ActionController::Base) + + it_does_not_leak_view_context +end + +describe "A mailer test" do + it_does_not_leak_view_context +end diff --git a/spec/dummy/test/decorators/test_unit/helpers_test.rb b/spec/dummy/test/decorators/test_unit/helpers_test.rb new file mode 100644 index 0000000..5232197 --- /dev/null +++ b/spec/dummy/test/decorators/test_unit/helpers_test.rb @@ -0,0 +1,15 @@ +require 'test_helper' + +class HelpersTest < Draper::TestCase + def test_access_helpers_through_helper + assert_equal "

Help!

", helper.content_tag(:p, "Help!") + end + + def test_access_helpers_through_helpers + assert_equal "

Help!

", helpers.content_tag(:p, "Help!") + end + + def test_access_helpers_through_h + assert_equal "

Help!

", h.content_tag(:p, "Help!") + end +end diff --git a/spec/dummy/test/decorators/test_unit/view_context_test.rb b/spec/dummy/test/decorators/test_unit/view_context_test.rb new file mode 100644 index 0000000..98b71a7 --- /dev/null +++ b/spec/dummy/test/decorators/test_unit/view_context_test.rb @@ -0,0 +1,24 @@ +require 'test_helper' + +def it_does_not_leak_view_context + 2.times do |n| + define_method("test_has_independent_view_context_#{n}") do + refute_equal :leaked, Draper::ViewContext.current + Draper::ViewContext.current = :leaked + end + end +end + +class DecoratorTest < Draper::TestCase + it_does_not_leak_view_context +end + +class ControllerTest < ActionController::TestCase + tests Class.new(ActionController::Base) + + it_does_not_leak_view_context +end + +class MailerTest < ActionMailer::TestCase + it_does_not_leak_view_context +end diff --git a/spec/dummy/test/minitest_helper.rb b/spec/dummy/test/minitest_helper.rb new file mode 100644 index 0000000..2b5462f --- /dev/null +++ b/spec/dummy/test/minitest_helper.rb @@ -0,0 +1,4 @@ +ENV['RAILS_ENV'] ||= 'test' +require File.expand_path('../../config/environment', __FILE__) +require 'minitest/autorun' +require 'minitest/rails' diff --git a/spec/dummy/test/test_helper.rb b/spec/dummy/test/test_helper.rb new file mode 100644 index 0000000..0f359de --- /dev/null +++ b/spec/dummy/test/test_helper.rb @@ -0,0 +1,3 @@ +ENV['RAILS_ENV'] ||= 'test' +require File.expand_path('../../config/environment', __FILE__) +require 'rails/test_help' diff --git a/spec/integration/integration_spec.rb b/spec/integration/integration_spec.rb index b1edfbc..4da2f5f 100644 --- a/spec/integration/integration_spec.rb +++ b/spec/integration/integration_spec.rb @@ -3,9 +3,13 @@ require 'support/dummy_app' require 'support/matchers/have_text' app = DummyApp.new(ENV["RAILS_ENV"]) +spec_types = { + view: ["/posts/1", "PostsController"], + mailer: ["/posts/1/mail", "PostMailer"] +} app.start_server do - {view: "/posts/1", mailer: "/posts/1/mail"}.each do |type, path| + spec_types.each do |type, (path, controller)| page = app.get(path) describe "in a #{type}" do @@ -13,6 +17,10 @@ app.start_server do expect(page).to have_text(app.environment).in("#environment") end + it "uses the correct view context controller" do + expect(page).to have_text(controller).in("#controller") + end + it "can use built-in helpers" do expect(page).to have_text("Once upon a...").in("#truncated") end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9a550a7..f485a3e 100755 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,7 @@ require 'bundler/setup' require 'draper' +require 'action_controller' +require 'action_controller/test_case' RSpec.configure do |config| config.treat_symbols_as_metadata_keys_with_true_values = true @@ -28,3 +30,7 @@ end def protect_class(klass) before { stub_const klass.name, Class.new(klass) } end + +def protect_module(mod) + before { stub_const mod.name, mod.dup } +end