parent
ef37241c10
commit
a039671202
16
README.md
16
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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -3,8 +3,12 @@ require 'rake/testtask'
|
|||
|
||||
RSpec::Core::RakeTask.new :rspec
|
||||
|
||||
RSpec::Core::RakeTask.new :fast_spec do |t|
|
||||
t.pattern = "fast_spec/**/*_spec.rb"
|
||||
end
|
||||
|
||||
Rake::TestTask.new :mini_test do |t|
|
||||
t.test_files = ["mini_test/mini_test_integration_test.rb"]
|
||||
end
|
||||
|
||||
task :default => [:rspec, :mini_test]
|
||||
task :default => [:rspec, :mini_test, :fast_spec]
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue