require 'spec_helper' module Draper Rspec.describe Factory do describe "#initialize" do it "accepts valid options" do valid_options = {with: Decorator, context: {foo: "bar"}} expect{Factory.new(valid_options)}.not_to raise_error end it "rejects invalid options" do expect{Factory.new(foo: "bar")}.to raise_error ArgumentError, /Unknown key/ end end describe "#decorate" do context "when object is nil" do it "returns nil" do factory = Factory.new expect(factory.decorate(nil)).to be_nil end end it "calls a worker" do factory = Factory.new worker = ->(*){ :decorated } expect(Factory::Worker).to receive(:new).and_return(worker) expect(factory.decorate(double)).to be :decorated end it "passes the object to the worker" do factory = Factory.new object = double expect(Factory::Worker).to receive(:new).with(anything(), object).and_return(->(*){}) factory.decorate(object) end context "when the :with option was given" do it "passes the decorator class to the worker" do decorator_class = double factory = Factory.new(with: decorator_class) expect(Factory::Worker).to receive(:new).with(decorator_class, anything()).and_return(->(*){}) factory.decorate(double) end end context "when the :with option was omitted" do it "passes nil to the worker" do factory = Factory.new expect(Factory::Worker).to receive(:new).with(nil, anything()).and_return(->(*){}) factory.decorate(double) end end it "passes options to the call" do factory = Factory.new worker = ->(*){} allow(Factory::Worker).to receive(:new).and_return(worker) options = {foo: "bar"} allow(worker).to receive(:call).with(options) factory.decorate(double, options) end context "when the :context option was given" do it "sets the passed context" do factory = Factory.new(context: {foo: "bar"}) worker = ->(*){} allow(Factory::Worker).to receive_messages new: worker expect(worker).to receive(:call).with(baz: "qux", context: {foo: "bar"}) factory.decorate(double, {baz: "qux"}) end it "is overridden by explicitly-specified context" do factory = Factory.new(context: {foo: "bar"}) worker = ->(*){} allow(Factory::Worker).to receive_messages new: worker expect(worker).to receive(:call).with(context: {baz: "qux"}) factory.decorate(double, context: {baz: "qux"}) end end end end Rspec.describe Factory::Worker do describe "#call" do it "calls the decorator method" do object = double options = {foo: "bar"} worker = Factory::Worker.new(double, object) decorator = ->(*){} allow(worker).to receive(:decorator){ decorator } allow(decorator).to receive(:call).with(object, options).and_return(:decorated) expect(worker.call(options)).to be :decorated end context "when the :context option is callable" do it "calls it" do worker = Factory::Worker.new(double, double) decorator = ->(*){} allow(worker).to receive_messages decorator: decorator context = {foo: "bar"} expect(decorator).to receive(:call).with(anything(), context: context) worker.call(context: ->{ context }) end it "receives arguments from the :context_args option" do worker = Factory::Worker.new(double, double) allow(worker).to receive_messages decorator: ->(*){} context = ->{} expect(context).to receive(:call).with(:foo, :bar) worker.call(context: context, context_args: [:foo, :bar]) end it "wraps non-arrays passed to :context_args" do worker = Factory::Worker.new(double, double) allow(worker).to receive_messages decorator: ->(*){} context = ->{} hash = {foo: "bar"} expect(context).to receive(:call).with(hash) worker.call(context: context, context_args: hash) end end context "when the :context option is not callable" do it "doesn't call it" do worker = Factory::Worker.new(double, double) decorator = ->(*){} allow(worker).to receive_messages decorator: decorator context = {foo: "bar"} expect(decorator).to receive(:call).with(anything(), context: context) worker.call(context: context) end end it "does not pass the :context_args option to the decorator" do worker = Factory::Worker.new(double, double) decorator = ->(*){} allow(worker).to receive_messages decorator: decorator expect(decorator).to receive(:call).with(anything(), foo: "bar") worker.call(foo: "bar", context_args: []) end end describe "#decorator" do context "for a singular object" do context "when decorator_class is specified" do it "returns the .decorate method from the decorator" do decorator_class = Class.new(Decorator) worker = Factory::Worker.new(decorator_class, double) expect(worker.decorator).to eq decorator_class.method(:decorate) end end context "when decorator_class is unspecified" do context "and the object is decoratable" do it "returns the object's #decorate method" do object = double options = {foo: "bar"} worker = Factory::Worker.new(nil, object) expect(object).to receive(:decorate).with(options).and_return(:decorated) expect(worker.decorator.call(object, options)).to be :decorated end end context "and the object is not decoratable" do it "raises an error" do object = double worker = Factory::Worker.new(nil, object) expect{worker.decorator}.to raise_error UninferrableDecoratorError end end end context "when the object is a struct" do it "returns a singular decorator" do object = Struct.new(:stuff).new("things") decorator_class = Class.new(Decorator) worker = Factory::Worker.new(decorator_class, object) expect(worker.decorator).to eq decorator_class.method(:decorate) end end end context "for a collection object" do context "when decorator_class is a CollectionDecorator" do it "returns the .decorate method from the collection decorator" do decorator_class = Class.new(CollectionDecorator) worker = Factory::Worker.new(decorator_class, []) expect(worker.decorator).to eq decorator_class.method(:decorate) end end context "when decorator_class is a Decorator" do it "returns the .decorate_collection method from the decorator" do decorator_class = Class.new(Decorator) worker = Factory::Worker.new(decorator_class, []) expect(worker.decorator).to eq decorator_class.method(:decorate_collection) end end context "when decorator_class is unspecified" do context "and the object is decoratable" do it "returns the .decorate_collection method from the object's decorator" do object = [] decorator_class = Class.new(Decorator) allow(object).to receive(:decorator_class){ decorator_class } allow(object).to receive(:decorate){ nil } worker = Factory::Worker.new(nil, object) expect(decorator_class).to receive(:decorate_collection).with(object, foo: "bar", with: nil).and_return(:decorated) expect(worker.decorator.call(object, foo: "bar")).to be :decorated end end context "and the object is not decoratable" do it "returns the .decorate method from CollectionDecorator" do worker = Factory::Worker.new(nil, []) expect(worker.decorator).to eq CollectionDecorator.method(:decorate) end end end end end end end