require 'spec_helper' describe 'transitions' do it 'should raise an exception when whiny' do process = ProcessWithNewDsl.new expect { process.stop! }.to raise_error do |err| expect(err.class).to eql(AASM::InvalidTransition) expect(err.message).to eql("Event 'stop' cannot transition from 'sleeping'.") expect(err.object).to eql(process) expect(err.event_name).to eql(:stop) end expect(process).to be_sleeping end it 'should not raise an exception when not whiny' do silencer = Silencer.new expect(silencer.smile!).to be_falsey expect(silencer).to be_silent end it 'should not raise an exception when superclass not whiny' do sub = SubClassing.new expect(sub.smile!).to be_falsey expect(sub).to be_silent end it 'should not raise an exception when from is nil even if whiny' do silencer = Silencer.new expect(silencer.smile_any!).to be_truthy expect(silencer).to be_smiling end it 'should call the block on success' do silencer = Silencer.new success = false expect { silencer.smile_any! do success = true end }.to change { success }.to(true) end it 'should not call the block on failure' do silencer = Silencer.new success = false expect { silencer.smile! do success = true end }.not_to change { success } end end describe AASM::Core::Transition do let(:state_machine) { AASM::StateMachine.new(:name) } let(:event) { AASM::Core::Event.new(:event, state_machine) } it 'should set from, to, and opts attr readers' do opts = {:from => 'foo', :to => 'bar', :guard => 'g'} st = AASM::Core::Transition.new(event, opts) expect(st.from).to eq(opts[:from]) expect(st.to).to eq(opts[:to]) expect(st.opts).to eq(opts) end it 'should set on_transition with deprecation warning' do opts = {:from => 'foo', :to => 'bar'} st = AASM::Core::Transition.allocate expect(st).to receive(:warn).with('[DEPRECATION] :on_transition is deprecated, use :after instead') st.send :initialize, event, opts do guard :gg on_transition :after_callback end expect(st.opts[:after]).to eql [:after_callback] end it 'should set after, guard and success from dsl' do opts = {:from => 'foo', :to => 'bar', :guard => 'g'} st = AASM::Core::Transition.new(event, opts) do guard :gg after :after_callback success :after_persist end expect(st.opts[:guard]).to eql ['g', :gg] expect(st.opts[:after]).to eql [:after_callback] # TODO fix this bad code coupling expect(st.opts[:success]).to eql [:after_persist] # TODO fix this bad code coupling end it 'should pass equality check if from and to are the same' do opts = {:from => 'foo', :to => 'bar', :guard => 'g'} st = AASM::Core::Transition.new(event, opts) obj = double('object') allow(obj).to receive(:from).and_return(opts[:from]) allow(obj).to receive(:to).and_return(opts[:to]) expect(st).to eq(obj) end it 'should fail equality check if from are not the same' do opts = {:from => 'foo', :to => 'bar', :guard => 'g'} st = AASM::Core::Transition.new(event, opts) obj = double('object') allow(obj).to receive(:from).and_return('blah') allow(obj).to receive(:to).and_return(opts[:to]) expect(st).not_to eq(obj) end it 'should fail equality check if to are not the same' do opts = {:from => 'foo', :to => 'bar', :guard => 'g'} st = AASM::Core::Transition.new(event, opts) obj = double('object') allow(obj).to receive(:from).and_return(opts[:from]) allow(obj).to receive(:to).and_return('blah') expect(st).not_to eq(obj) end end describe AASM::Core::Transition, '- when performing guard checks' do let(:state_machine) { AASM::StateMachine.new(:name) } let(:event) { AASM::Core::Event.new(:event, state_machine) } it 'should return true of there is no guard' do opts = {:from => 'foo', :to => 'bar'} st = AASM::Core::Transition.new(event, opts) expect(st.allowed?(nil)).to be_truthy end it 'should call the method on the object if guard is a symbol' do opts = {:from => 'foo', :to => 'bar', :guard => :test} st = AASM::Core::Transition.new(event, opts) obj = double('object') expect(obj).to receive(:test) expect(st.allowed?(obj)).to be false end it 'should add the name of the failed method calls to the failures instance var' do opts = {:from => 'foo', :to => 'bar', :guard => :test} st = AASM::Core::Transition.new(event, opts) obj = double('object') expect(obj).to receive(:test) st.allowed?(obj) expect(st.failures).to eq [:test] end it 'should call the method on the object if unless is a symbol' do opts = {:from => 'foo', :to => 'bar', :unless => :test} st = AASM::Core::Transition.new(event, opts) obj = double('object') expect(obj).to receive(:test) expect(st.allowed?(obj)).to be true end it 'should call the method on the object if guard is a string' do opts = {:from => 'foo', :to => 'bar', :guard => 'test'} st = AASM::Core::Transition.new(event, opts) obj = double('object') expect(obj).to receive(:test) expect(st.allowed?(obj)).to be false end it 'should call the method on the object if unless is a string' do opts = {:from => 'foo', :to => 'bar', :unless => 'test'} st = AASM::Core::Transition.new(event, opts) obj = double('object') expect(obj).to receive(:test) expect(st.allowed?(obj)).to be true end it 'should call the proc passing the object if the guard is a proc' do opts = {:from => 'foo', :to => 'bar', :guard => Proc.new { test }} st = AASM::Core::Transition.new(event, opts) obj = double('object') expect(obj).to receive(:test) expect(st.allowed?(obj)).to be false end end describe AASM::Core::Transition, '- when executing the transition with a Proc' do let(:state_machine) { AASM::StateMachine.new(:name) } let(:event) { AASM::Core::Event.new(:event, state_machine) } it 'should call a Proc on the object with args' do opts = {:from => 'foo', :to => 'bar', :after => Proc.new {|a| test(a) }} st = AASM::Core::Transition.new(event, opts) args = {:arg1 => '1', :arg2 => '2'} obj = double('object', :aasm => 'aasm') expect(obj).to receive(:test).with(args) st.execute(obj, args) end it 'should call a Proc on the object without args' do # in order to test that the Proc has been called, we make sure # that after running the :after callback the prc_object is set prc_object = nil prc = Proc.new { prc_object = self } opts = {:from => 'foo', :to => 'bar', :after => prc } st = AASM::Core::Transition.new(event, opts) args = {:arg1 => '1', :arg2 => '2'} obj = double('object', :aasm => 'aasm') st.execute(obj, args) expect(prc_object).to eql obj end end describe AASM::Core::Transition, '- when executing the transition with an :after method call' do let(:state_machine) { AASM::StateMachine.new(:name) } let(:event) { AASM::Core::Event.new(:event, state_machine) } it 'should accept a String for the method name' do opts = {:from => 'foo', :to => 'bar', :after => 'test'} st = AASM::Core::Transition.new(event, opts) args = {:arg1 => '1', :arg2 => '2'} obj = double('object', :aasm => 'aasm') expect(obj).to receive(:test) st.execute(obj, args) end it 'should accept a Symbol for the method name' do opts = {:from => 'foo', :to => 'bar', :after => :test} st = AASM::Core::Transition.new(event, opts) args = {:arg1 => '1', :arg2 => '2'} obj = double('object', :aasm => 'aasm') expect(obj).to receive(:test) st.execute(obj, args) end it 'should pass args if the target method accepts them' do opts = {:from => 'foo', :to => 'bar', :after => :test} st = AASM::Core::Transition.new(event, opts) args = {:arg1 => '1', :arg2 => '2'} obj = double('object', :aasm => 'aasm') def obj.test(args) "arg1: #{args[:arg1]} arg2: #{args[:arg2]}" end return_value = st.execute(obj, args) expect(return_value).to eq('arg1: 1 arg2: 2') end it 'should NOT pass args if the target method does NOT accept them' do opts = {:from => 'foo', :to => 'bar', :after => :test} st = AASM::Core::Transition.new(event, opts) args = {:arg1 => '1', :arg2 => '2'} obj = double('object', :aasm => 'aasm') def obj.test 'success' end return_value = st.execute(obj, args) expect(return_value).to eq('success') end it 'should allow accessing the from_state and the to_state' do opts = {:from => 'foo', :to => 'bar', :after => :test} transition = AASM::Core::Transition.new(event, opts) args = {:arg1 => '1', :arg2 => '2'} obj = double('object', :aasm => AASM::InstanceBase.new('object')) def obj.test(args) "from: #{aasm.from_state} to: #{aasm.to_state}" end return_value = transition.execute(obj, args) expect(return_value).to eq('from: foo to: bar') end end describe AASM::Core::Transition, '- when executing the transition with a Class' do let(:state_machine) { AASM::StateMachine.new(:name) } let(:event) { AASM::Core::Event.new(:event, state_machine) } class AfterTransitionClass def initialize(record) @record = record end def call "from: #{@record.aasm.from_state} to: #{@record.aasm.to_state}" end end class AfterTransitionClassWithArgs def initialize(record, args) @record = record @args = args end def call "arg1: #{@args[:arg1]}, arg2: #{@args[:arg2]}" end end class AfterTransitionClassWithoutArgs def call 'success' end end it 'passes the record to the initialize method on the class to give access to the from_state and to_state' do opts = {:from => 'foo', :to => 'bar', :after => AfterTransitionClass} transition = AASM::Core::Transition.new(event, opts) obj = double('object', :aasm => AASM::InstanceBase.new('object')) return_value = transition.execute(obj) expect(return_value).to eq('from: foo to: bar') end it 'should pass args to the initialize method on the class if it accepts them' do opts = {:from => 'foo', :to => 'bar', :after => AfterTransitionClassWithArgs} st = AASM::Core::Transition.new(event, opts) args = {:arg1 => '1', :arg2 => '2'} obj = double('object', :aasm => 'aasm') return_value = st.execute(obj, args) expect(return_value).to eq('arg1: 1, arg2: 2') end it 'should NOT pass args if the call method of the class if it does NOT accept them' do opts = {:from => 'foo', :to => 'bar', :after => AfterTransitionClassWithoutArgs} st = AASM::Core::Transition.new(event, opts) obj = double('object', :aasm => 'aasm') return_value = st.execute(obj) expect(return_value).to eq('success') end end describe AASM::Core::Transition, '- when invoking the transition :success method call' do let(:state_machine) { AASM::StateMachine.new(:name) } let(:event) { AASM::Core::Event.new(:event, state_machine) } it 'should accept a String for the method name' do opts = {:from => 'foo', :to => 'bar', :success => 'test'} st = AASM::Core::Transition.new(event, opts) args = {:arg1 => '1', :arg2 => '2'} obj = double('object', :aasm => 'aasm') expect(obj).to receive(:test) st.invoke_success_callbacks(obj, args) end it 'should accept a Symbol for the method name' do opts = {:from => 'foo', :to => 'bar', :success => :test} st = AASM::Core::Transition.new(event, opts) args = {:arg1 => '1', :arg2 => '2'} obj = double('object', :aasm => 'aasm') expect(obj).to receive(:test) st.invoke_success_callbacks(obj, args) end it 'should accept a Array for the method name' do opts = {:from => 'foo', :to => 'bar', :success => [:test1, :test2]} st = AASM::Core::Transition.new(event, opts) args = {:arg1 => '1', :arg2 => '2'} obj = double('object', :aasm => 'aasm') expect(obj).to receive(:test1) expect(obj).to receive(:test2) st.invoke_success_callbacks(obj, args) end it 'should pass args if the target method accepts them' do opts = {:from => 'foo', :to => 'bar', :success => :test} st = AASM::Core::Transition.new(event, opts) args = {:arg1 => '1', :arg2 => '2'} obj = double('object', :aasm => 'aasm') def obj.test(args) "arg1: #{args[:arg1]} arg2: #{args[:arg2]}" end return_value = st.invoke_success_callbacks(obj, args) expect(return_value).to eq('arg1: 1 arg2: 2') end it 'should NOT pass args if the target method does NOT accept them' do opts = {:from => 'foo', :to => 'bar', :success => :test} st = AASM::Core::Transition.new(event, opts) args = {:arg1 => '1', :arg2 => '2'} obj = double('object', :aasm => 'aasm') def obj.test 'success' end return_value = st.invoke_success_callbacks(obj, args) expect(return_value).to eq('success') end end