require 'spec_helper'
describe Draper::Decorator do
before { ApplicationController.new.view_context }
subject { decorator_class.new(source) }
let(:decorator_class) { Draper::Decorator }
let(:source) { Product.new }
describe "#initialize" do
describe "options validation" do
let(:valid_options) { {context: {}} }
it "does not raise error on valid options" do
expect { decorator_class.new(source, valid_options) }.to_not raise_error
end
it "raises error on invalid options" do
expect { decorator_class.new(source, valid_options.merge(foo: 'bar')) }.to raise_error(ArgumentError, 'Unknown key: foo')
end
end
it "sets the source" do
subject.source.should be source
end
it "stores context" do
decorator = decorator_class.new(source, context: {some: 'context'})
decorator.context.should == {some: 'context'}
end
context "when decorating an instance of itself" do
it "does not redecorate" do
decorator = ProductDecorator.new(source)
ProductDecorator.new(decorator).source.should be source
end
context "when options are supplied" do
it "overwrites existing context" do
decorator = ProductDecorator.new(source, context: {role: :admin})
ProductDecorator.new(decorator, context: {role: :user}).context.should == {role: :user}
end
end
context "when no options are supplied" do
it "preserves existing context" do
decorator = ProductDecorator.new(source, context: {role: :admin})
ProductDecorator.new(decorator).context.should == {role: :admin}
end
end
end
it "decorates other decorators" do
decorator = ProductDecorator.new(source)
SpecificProductDecorator.new(decorator).source.should be decorator
end
it "warns if target is already decorated with the same decorator class" do
warning_message = nil
Object.any_instance.stub(:warn) { |message| warning_message = message }
deep_decorator = SpecificProductDecorator.new(ProductDecorator.new(Product.new))
expect {
ProductDecorator.new(deep_decorator)
}.to change { warning_message }
warning_message.should =~ /ProductDecorator/
warning_message.should include caller(1).first
end
end
describe "#context=" do
it "modifies the context" do
decorator = decorator_class.new(source, context: {some: 'context'})
decorator.context = {some: 'other_context'}
decorator.context.should == {some: 'other_context'}
end
end
describe ".decorate_collection" do
subject { ProductDecorator.decorate_collection(source) }
let(:source) { [Product.new, Widget.new] }
describe "options validation" do
let(:valid_options) { {with: :infer, context: {}} }
before(:each) { Draper::CollectionDecorator.stub(:new) }
it "does not raise error on valid options" do
expect { ProductDecorator.decorate_collection(source, valid_options) }.to_not raise_error
end
it "raises error on invalid options" do
expect { ProductDecorator.decorate_collection(source, valid_options.merge(foo: 'bar')) }.to raise_error(ArgumentError, 'Unknown key: foo')
end
end
it "returns a collection decorator" do
subject.should be_a Draper::CollectionDecorator
subject.should == source
end
it "uses itself as the item decorator by default" do
subject.each {|item| item.should be_a ProductDecorator}
end
context "with context" do
subject { ProductDecorator.decorate_collection(source, with: :infer, context: {some: 'context'}) }
it "passes the context to the collection decorator" do
subject.context.should == {some: 'context'}
end
end
end
describe "#helpers" do
it "returns a HelperProxy" do
subject.helpers.should be_a Draper::HelperProxy
end
it "is aliased to #h" do
subject.h.should be subject.helpers
end
it "initializes the wrapper only once" do
helper_proxy = subject.helpers
helper_proxy.stub(:test_method) { "test_method" }
subject.helpers.test_method.should == "test_method"
subject.helpers.test_method.should == "test_method"
end
end
describe "#localize" do
before { subject.helpers.should_receive(:localize).with(:an_object, {some: 'parameter'}) }
it "delegates to #helpers" do
subject.localize(:an_object, some: 'parameter')
end
it "is aliased to #l" do
subject.l(:an_object, some: 'parameter')
end
end
describe ".helpers" do
it "returns a HelperProxy" do
subject.class.helpers.should be_a Draper::HelperProxy
end
it "is aliased to .h" do
subject.class.h.should be subject.class.helpers
end
end
describe ".decorates" do
subject { Class.new(Draper::Decorator) }
context "with a symbol" do
it "sets .source_class" do
subject.decorates :product
subject.source_class.should be Product
end
end
context "with a string" do
it "sets .source_class" do
subject.decorates "product"
subject.source_class.should be Product
end
end
context "with a class" do
it "sets .source_class" do
subject.decorates Product
subject.source_class.should be Product
end
end
end
describe ".source_class" do
context "when not set by .decorates" do
context "for an anonymous decorator" do
subject { Class.new(Draper::Decorator) }
it "raises an UninferrableSourceError" do
expect{subject.source_class}.to raise_error Draper::UninferrableSourceError
end
end
context "for a decorator without a corresponding source" do
subject { SpecificProductDecorator }
it "raises an UninferrableSourceError" do
expect{subject.source_class}.to raise_error Draper::UninferrableSourceError
end
end
context "for a decorator called Decorator" do
subject { Draper::Decorator }
it "raises an UninferrableSourceError" do
expect{subject.source_class}.to raise_error Draper::UninferrableSourceError
end
end
context "for a decorator with a name not ending in Decorator" do
subject { DecoratorWithApplicationHelper }
it "raises an UninferrableSourceError" do
expect{subject.source_class}.to raise_error Draper::UninferrableSourceError
end
end
context "for an inferrable source" do
subject { ProductDecorator }
it "infers the source" do
subject.source_class.should be Product
end
end
context "for a namespaced inferrable source" do
subject { Namespace::ProductDecorator }
it "infers the namespaced source" do
subject.source_class.should be Namespace::Product
end
end
end
end
describe ".source_class?" do
subject { Class.new(Draper::Decorator) }
it "returns truthy when .source_class is set" do
subject.stub(:source_class).and_return(Product)
subject.source_class?.should be_true
end
it "returns false when .source_class is not inferrable" do
subject.stub(:source_class).and_raise(Draper::UninferrableSourceError.new(subject))
subject.source_class?.should be_false
end
end
describe ".decorates_association" do
let(:decorator_class) { Class.new(ProductDecorator) }
before { decorator_class.decorates_association :similar_products, with: ProductDecorator }
describe "overridden association method" do
let(:decorated_association) { ->{} }
describe "options validation" do
let(:valid_options) { {with: ProductDecorator, scope: :foo, context: {}} }
before(:each) { Draper::DecoratedAssociation.stub(:new).and_return(decorated_association) }
it "does not raise error on valid options" do
expect { decorator_class.decorates_association :similar_products, valid_options }.to_not raise_error
end
it "raises error on invalid options" do
expect { decorator_class.decorates_association :similar_products, valid_options.merge(foo: 'bar') }.to raise_error(ArgumentError, 'Unknown key: foo')
end
end
it "creates a DecoratedAssociation" do
Draper::DecoratedAssociation.should_receive(:new).with(subject, :similar_products, {with: ProductDecorator}).and_return(decorated_association)
subject.similar_products
end
it "receives the Decorator" do
Draper::DecoratedAssociation.should_receive(:new).with(kind_of(decorator_class), :similar_products, {with: ProductDecorator}).and_return(decorated_association)
subject.similar_products
end
it "memoizes the DecoratedAssociation" do
Draper::DecoratedAssociation.should_receive(:new).once.and_return(decorated_association)
subject.similar_products
subject.similar_products
end
it "calls the DecoratedAssociation" do
Draper::DecoratedAssociation.stub(:new).and_return(decorated_association)
decorated_association.should_receive(:call).and_return(:decorated)
subject.similar_products.should be :decorated
end
end
end
describe ".decorates_associations" do
subject { decorator_class }
it "decorates each of the associations" do
subject.should_receive(:decorates_association).with(:similar_products, {})
subject.should_receive(:decorates_association).with(:previous_version, {})
subject.decorates_associations :similar_products, :previous_version
end
it "dispatches options" do
subject.should_receive(:decorates_association).with(:similar_products, {with: ProductDecorator})
subject.should_receive(:decorates_association).with(:previous_version, {with: ProductDecorator})
subject.decorates_associations :similar_products, :previous_version, with: ProductDecorator
end
end
describe "#applied_decorators" do
it "returns a list of decorators applied to a model" do
decorator = ProductDecorator.new(SpecificProductDecorator.new(Product.new))
decorator.applied_decorators.should == [SpecificProductDecorator, ProductDecorator]
end
end
describe "#decorated_with?" do
it "checks if a decorator has been applied to a model" do
decorator = ProductDecorator.new(SpecificProductDecorator.new(Product.new))
decorator.should be_decorated_with ProductDecorator
decorator.should be_decorated_with SpecificProductDecorator
decorator.should_not be_decorated_with WidgetDecorator
end
end
describe "#decorated?" do
it "returns true" do
subject.should be_decorated
end
end
describe "#source" do
it "returns the wrapped object" do
subject.source.should be source
end
it "is aliased to #to_source" do
subject.to_source.should be source
end
it "is aliased to #model" do
subject.model.should be source
end
end
describe "#to_model" do
it "returns the decorator" do
subject.to_model.should be subject
end
end
describe "#to_param" do
it "proxies to the source" do
source.stub(:to_param).and_return(42)
subject.to_param.should == 42
end
end
describe "#==" do
context "with itself" do
it "returns true" do
(subject == subject).should be_true
end
end
context "with another decorator having the same source" do
it "returns true" do
(subject == ProductDecorator.new(source)).should be_true
end
end
context "with another decorator having a different source" do
it "returns false" do
(subject == ProductDecorator.new(Object.new)).should be_false
end
end
context "with the source object" do
it "returns true" do
(subject == source).should be_true
end
end
context "with another object" do
it "returns false" do
(subject == Object.new).should be_false
end
end
end
describe "#===" do
context "with itself" do
it "returns true" do
(subject === subject).should be_true
end
end
context "with another decorator having the same source" do
it "returns true" do
(subject === ProductDecorator.new(source)).should be_true
end
end
context "with another decorator having a different source" do
it "returns false" do
(subject === ProductDecorator.new(Object.new)).should be_false
end
end
context "with the source object" do
it "returns true" do
(subject === source).should be_true
end
end
context "with another object" do
it "returns false" do
(subject === Object.new).should be_false
end
end
end
describe "#respond_to?" do
let(:decorator_class) { Class.new(ProductDecorator) }
it "returns true for its own methods" do
subject.should respond_to :awesome_title
end
it "returns true for the source's methods" do
subject.should respond_to :title
end
context "with include_private" do
it "returns true for its own private methods" do
subject.respond_to?(:awesome_private_title, true).should be_true
end
it "returns false for the source's private methods" do
subject.respond_to?(:private_title, true).should be_false
end
end
context "with method security" do
it "respects allows" do
subject.class.allows :hello_world
subject.should respond_to :hello_world
subject.should_not respond_to :goodnight_moon
end
it "respects denies" do
subject.class.denies :goodnight_moon
subject.should respond_to :hello_world
subject.should_not respond_to :goodnight_moon
end
it "respects denies_all" do
subject.class.denies_all
subject.should_not respond_to :hello_world
subject.should_not respond_to :goodnight_moon
end
end
end
describe ".respond_to?" do
subject { Class.new(ProductDecorator) }
context "without a source class" do
it "returns true for its own class methods" do
subject.should respond_to :my_class_method
end
it "returns false for other class methods" do
subject.should_not respond_to :sample_class_method
end
end
context "with a source_class" do
before { subject.decorates :product }
it "returns true for its own class methods" do
subject.should respond_to :my_class_method
end
it "returns true for the source's class methods" do
subject.should respond_to :sample_class_method
end
end
end
describe "proxying" do
context "instance methods" do
let(:decorator_class) { Class.new(ProductDecorator) }
it "does not proxy methods that are defined on the decorator" do
subject.overridable.should be :overridden
end
it "does not proxy methods inherited from Object" do
subject.inspect.should_not be source.inspect
end
it "proxies missing methods that exist on the source" do
source.stub(:hello_world).and_return(:proxied)
subject.hello_world.should be :proxied
end
it "adds proxied methods to the decorator when they are used" do
subject.methods.should_not include :hello_world
subject.hello_world
subject.methods.should include :hello_world
end
it "passes blocks to proxied methods" do
subject.block{"marker"}.should == "marker"
end
it "does not confuse Kernel#Array" do
Array(subject).should be_a Array
end
it "proxies delegated methods" do
subject.delegated_method.should == "Yay, delegation"
end
it "does not proxy private methods" do
expect{subject.private_title}.to raise_error NoMethodError
end
context "with method security" do
it "respects allows" do
source.stub(:hello_world, :goodnight_moon).and_return(:proxied)
subject.class.allows :hello_world
subject.hello_world.should be :proxied
expect{subject.goodnight_moon}.to raise_error NameError
end
it "respects denies" do
source.stub(:hello_world, :goodnight_moon).and_return(:proxied)
subject.class.denies :goodnight_moon
subject.hello_world.should be :proxied
expect{subject.goodnight_moon}.to raise_error NameError
end
it "respects denies_all" do
source.stub(:hello_world, :goodnight_moon).and_return(:proxied)
subject.class.denies_all
expect{subject.hello_world}.to raise_error NameError
expect{subject.goodnight_moon}.to raise_error NameError
end
end
end
context "class methods" do
subject { Class.new(ProductDecorator) }
let(:source_class) { Product }
before { subject.decorates source_class }
it "does not proxy methods that are defined on the decorator" do
subject.overridable.should be :overridden
end
it "proxies missing methods that exist on the source" do
source_class.stub(:hello_world).and_return(:proxied)
subject.hello_world.should be :proxied
end
end
end
describe "method security" do
subject(:decorator_class) { Draper::Decorator }
let(:security) { stub }
before { decorator_class.stub(:security).and_return(security) }
it "delegates .denies to Draper::Security" do
security.should_receive(:denies).with(:foo, :bar)
decorator_class.denies :foo, :bar
end
it "delegates .denies_all to Draper::Security" do
security.should_receive(:denies_all)
decorator_class.denies_all
end
it "delegates .allows to Draper::Security" do
security.should_receive(:allows).with(:foo, :bar)
decorator_class.allows :foo, :bar
end
end
context "in a Rails application" do
let(:decorator_class) { DecoratorWithApplicationHelper }
it "has access to ApplicationHelper helpers" do
subject.uses_hello_world.should == "Hello, World!"
end
it "is able to use the content_tag helper" do
subject.sample_content.to_s.should == "Hello, World!"
end
it "is able to use the link_to helper" do
subject.sample_link.should == %{Hello}
end
it "is able to use the truncate helper" do
subject.sample_truncate.should == "Once..."
end
it "is able to access html_escape, a private method" do
subject.sample_html_escaped_text.should == '<script>danger</script>'
end
end
it "pretends to be the source class" do
subject.kind_of?(source.class).should be_true
subject.is_a?(source.class).should be_true
end
it "is still its own class" do
subject.kind_of?(subject.class).should be_true
subject.is_a?(subject.class).should be_true
end
describe ".decorates_finders" do
it "extends the Finders module" do
ProductDecorator.should be_a_kind_of Draper::Finders
end
end
describe "#serializable_hash" do
let(:decorator_class) { ProductDecorator }
it "serializes overridden attributes" do
subject.serializable_hash[:overridable].should be :overridden
end
end
describe ".method_missing" do
context "when called on an anonymous decorator" do
subject { ->{ Class.new(Draper::Decorator).fizzbuzz } }
it { should raise_error NoMethodError }
end
context "when called on an uninferrable decorator" do
subject { ->{ SpecificProductDecorator.fizzbuzz } }
it { should raise_error NoMethodError }
end
context "when called on an inferrable decorator" do
context "for a method known to the inferred class" do
subject { ->{ ProductDecorator.model_name } }
it { should_not raise_error }
end
context "for a method unknown to the inferred class" do
subject { ->{ ProductDecorator.fizzbuzz } }
it { should raise_error NoMethodError }
end
end
end
end