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
it "sets the source" do
subject.source.should be source
end
it "stores options" do
decorator = decorator_class.new(source, some: "options")
decorator.options.should == {some: "options"}
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 options" do
decorator = ProductDecorator.new(source, role: :admin)
ProductDecorator.new(decorator, role: :user).options.should == {role: :user}
end
end
context "when no options are supplied" do
it "preserves existing options" do
decorator = ProductDecorator.new(source, role: :admin)
ProductDecorator.new(decorator).options.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 ".decorate_collection" do
subject { ProductDecorator.decorate_collection(source) }
let(:source) { [Product.new, Widget.new] }
it "returns a collection decorator" do
subject.should be_a Draper::CollectionDecorator
subject.source.should be source
end
it "uses itself as the item decorator by default" do
subject.each {|item| item.should be_a ProductDecorator}
end
context "when given :with => :infer" do
subject { ProductDecorator.decorate_collection(source, with: :infer) }
it "infers the item decorators" do
subject.first.should be_a ProductDecorator
subject.last.should be_a WidgetDecorator
end
end
context "with options" do
subject { ProductDecorator.decorate_collection(source, with: :infer, some: "options") }
it "passes the options to the collection decorator" do
subject.options.should == {some: "options"}
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: "options"}) }
it "delegates to #helpers" do
subject.localize(:an_object, some: "options")
end
it "is aliased to #l" do
subject.l(:an_object, some: "options")
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_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) { ->{} }
it "creates a DecoratedAssociation" do
Draper::DecoratedAssociation.should_receive(:new).with(source, :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 true for the source's private methods" do
subject.respond_to?(:private_title, true).should be_true
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 "method proxying" 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
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
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 ".has_finders" do
it "extends the Finders module" do
ProductDecorator.should be_a_kind_of Draper::Finders
end
context "with no options" do
it "infers the finder class" do
ProductDecorator.finder_class.should be Product
end
context "for a namespaced model" do
it "infers the finder class" do
Namespace::ProductDecorator.finder_class.should be Namespace::Product
end
end
end
context "with :for option" do
subject { Class.new(Draper::Decorator) }
context "with a symbol" do
it "sets the finder class" do
subject.has_finders for: :product
subject.finder_class.should be Product
end
end
context "with a string" do
it "sets the finder class" do
subject.has_finders for: "some_thing"
subject.finder_class.should be SomeThing
end
end
context "with a class" do
it "sets the finder_class" do
subject.has_finders for: Namespace::Product
subject.finder_class.should be Namespace::Product
end
end
end
end
describe "#serializable_hash" do
let(:decorator_class) { ProductDecorator }
it "serializes overridden attributes" do
subject.serializable_hash[:overridable].should be :overridden
end
end
end