require 'spec_helper' require 'support/shared_examples/decoratable_equality' module Draper describe Decoratable do describe "#decorate" do it "returns a decorator for self" do product = Product.new decorator = product.decorate expect(decorator).to be_a ProductDecorator expect(decorator.object).to be product end it "accepts context" do context = {some: "context"} decorator = Product.new.decorate(context: context) expect(decorator.context).to be context end it "uses the #decorator_class" do product = Product.new allow(product).to receive_messages decorator_class: OtherDecorator expect(product.decorate).to be_an_instance_of OtherDecorator end end describe "#applied_decorators" do it "returns an empty list" do expect(Product.new.applied_decorators).to eq [] end end describe "#decorated_with?" do it "returns false" do expect(Product.new).not_to be_decorated_with Decorator end end describe "#decorated?" do it "returns false" do expect(Product.new).not_to be_decorated end end describe "#decorator_class?" do it "returns true for decoratable model" do expect(Product.new.decorator_class?).to be_truthy end it "returns false for non-decoratable model" do expect(Model.new.decorator_class?).to be_falsey end end describe ".decorator_class?" do it "returns true for decoratable model" do expect(Product.decorator_class?).to be_truthy end it "returns false for non-decoratable model" do expect(Model.decorator_class?).to be_falsey end end describe "#decorator_class" do it "delegates to .decorator_class" do product = Product.new expect(Product).to receive(:decorator_class).and_return(:some_decorator) expect(product.decorator_class).to be :some_decorator end it "specifies the class that #decorator_class was first called on (superclass)" do person = Person.new expect { person.decorator_class }.to raise_error(Draper::UninferrableDecoratorError, 'Could not infer a decorator for Person.') end it "specifies the class that #decorator_class was first called on (subclass)" do child = Child.new expect { child.decorator_class }.to raise_error(Draper::UninferrableDecoratorError, 'Could not infer a decorator for Child.') end end describe "#==" do it_behaves_like "decoration-aware #==", Product.new end describe "#===" do it "is true when #== is true" do product = Product.new expect(product).to receive(:==).and_return(true) expect(product === :anything).to be_truthy end it "is false when #== is false" do product = Product.new expect(product).to receive(:==).and_return(false) expect(product === :anything).to be_falsey end end describe ".====" do it "is true for an instance" do expect(Product === Product.new).to be_truthy end it "is true for a derived instance" do expect(Product === Class.new(Product).new).to be_truthy end it "is false for an unrelated instance" do expect(Product === Model.new).to be_falsey end it "is true for a decorated instance" do decorator = Product.new.decorate expect(Product === decorator).to be_truthy end it "is true for a decorated derived instance" do decorator = Class.new(Product).new.decorate expect(Product === decorator).to be_truthy end it "is false for a decorated unrelated instance" do decorator = Other.new.decorate expect(Product === decorator).to be_falsey end it "is false for a non-decorator which happens to respond to object" do decorator = double(object: Product.new) expect(Product === decorator).to be_falsey end end describe ".decorate" do it "calls #decorate_collection on .decorator_class" do scoped = [Product.new] allow(Product).to receive(:all).and_return(scoped) expect(Product.decorator_class).to receive(:decorate_collection).with(scoped, with: nil).and_return(:decorated_collection) expect(Product.decorate).to be :decorated_collection end it "accepts options" do options = {with: ProductDecorator, context: {some: "context"}} allow(Product).to receive(:all).and_return([]) expect(Product.decorator_class).to receive(:decorate_collection).with([], options) Product.decorate(options) end end describe ".decorator_class" do context "for classes" do it "infers the decorator from the class" do expect(Product.decorator_class).to be ProductDecorator end context "without a decorator on its own" do it "infers the decorator from a superclass" do expect(SpecialProduct.decorator_class).to be ProductDecorator end end end context "for ActiveModel classes" do it "infers the decorator from the model name" do allow(Product).to receive(:model_name){"Other"} expect(Product.decorator_class).to be OtherDecorator end end context "in a namespace" do context "for classes" do it "infers the decorator from the class" do expect(Namespaced::Product.decorator_class).to be Namespaced::ProductDecorator end end context "for ActiveModel classes" do it "infers the decorator from the model name" do allow(Namespaced::Product).to receive(:model_name).and_return("Namespaced::Other") expect(Namespaced::Product.decorator_class).to be Namespaced::OtherDecorator end end end context "when the decorator contains name error" do it "throws an NameError" do # We imitate ActiveSupport::Autoload behavior here in order to cause lazy NameError exception raising allow_any_instance_of(Module).to receive(:const_missing) { Class.new { any_nonexisting_method_name } } expect{Model.decorator_class}.to raise_error { |error| expect(error).to be_an_instance_of(NameError) } end end context "when the decorator can't be inferred" do it "throws an UninferrableDecoratorError" do expect{Model.decorator_class}.to raise_error UninferrableDecoratorError end end context "when an unrelated NameError is thrown" do it "re-raises that error" do # Not related to safe_constantize behavior, we just want to raise a NameError inside the function allow_any_instance_of(String).to receive(:safe_constantize) { Draper::Base } expect{Product.decorator_class}.to raise_error NameError, /Draper::Base/ end end context "when an anonymous class is given" do it "infers the decorator from a superclass" do anonymous_class = Class.new(Product) do def self.name to_s end end expect(anonymous_class.decorator_class).to be ProductDecorator end end end end end