require File.expand_path(File.dirname(__FILE__) + '/../test_helper') class TransitionTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_have_an_object assert_equal @object, @transition.object end def test_should_have_a_machine assert_equal @machine, @transition.machine end def test_should_have_an_event assert_equal :ignite, @transition.event end def test_should_have_a_qualified_event assert_equal :ignite, @transition.qualified_event end def test_should_have_a_human_event assert_equal 'ignite', @transition.human_event end def test_should_have_a_from_value assert_equal 'parked', @transition.from end def test_should_have_a_from_name assert_equal :parked, @transition.from_name end def test_should_have_a_qualified_from_name assert_equal :parked, @transition.qualified_from_name end def test_should_have_a_human_from_name assert_equal 'parked', @transition.human_from_name end def test_should_have_a_to_value assert_equal 'idling', @transition.to end def test_should_have_a_to_name assert_equal :idling, @transition.to_name end def test_should_have_a_qualified_to_name assert_equal :idling, @transition.qualified_to_name end def test_should_have_a_human_to_name assert_equal 'idling', @transition.human_to_name end def test_should_have_an_attribute assert_equal :state, @transition.attribute end def test_should_not_have_an_action assert_nil @transition.action end def test_should_not_be_transient assert_equal false, @transition.transient? end def test_should_generate_attributes expected = {:object => @object, :attribute => :state, :event => :ignite, :from => 'parked', :to => 'idling'} assert_equal expected, @transition.attributes end def test_should_have_empty_args assert_equal [], @transition.args end def test_should_not_have_a_result assert_nil @transition.result end def test_should_use_pretty_inspect assert_equal '#', @transition.inspect end end class TransitionWithInvalidNodesTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' end def test_should_raise_exception_without_event assert_raise(IndexError) { StateMachine::Transition.new(@object, @machine, nil, :parked, :idling) } end def test_should_raise_exception_with_invalid_event assert_raise(IndexError) { StateMachine::Transition.new(@object, @machine, :invalid, :parked, :idling) } end def test_should_raise_exception_with_invalid_from_state assert_raise(IndexError) { StateMachine::Transition.new(@object, @machine, :ignite, :invalid, :idling) } end def test_should_raise_exception_with_invalid_to_state assert_raise(IndexError) { StateMachine::Transition.new(@object, @machine, :ignite, :parked, :invalid) } end end class TransitionWithDynamicToValueTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked @machine.state :idling, :value => lambda {1} @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_evaluate_to_value assert_equal 1, @transition.to end end class TransitionLoopbackTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked @machine.event :park @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :park, :parked, :parked) end def test_should_be_loopback assert @transition.loopback? end end class TransitionWithDifferentStatesTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_not_be_loopback assert !@transition.loopback? end end class TransitionWithNamespaceTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :namespace => 'alarm') @machine.state :off, :active @machine.event :activate @object = @klass.new @object.state = 'off' @transition = StateMachine::Transition.new(@object, @machine, :activate, :off, :active) end def test_should_have_an_event assert_equal :activate, @transition.event end def test_should_have_a_qualified_event assert_equal :activate_alarm, @transition.qualified_event end def test_should_have_a_from_name assert_equal :off, @transition.from_name end def test_should_have_a_qualified_from_name assert_equal :alarm_off, @transition.qualified_from_name end def test_should_have_a_human_from_name assert_equal 'off', @transition.human_from_name end def test_should_have_a_to_name assert_equal :active, @transition.to_name end def test_should_have_a_qualified_to_name assert_equal :alarm_active, @transition.qualified_to_name end def test_should_have_a_human_to_name assert_equal 'active', @transition.human_to_name end end class TransitionWithCustomMachineAttributeTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :state, :attribute => :state_id) @machine.state :off, :value => 1 @machine.state :active, :value => 2 @machine.event :activate @object = @klass.new @object.state_id = 1 @transition = StateMachine::Transition.new(@object, @machine, :activate, :off, :active) end def test_should_persist @transition.persist assert_equal 2, @object.state_id end def test_should_rollback @object.state_id = 2 @transition.rollback assert_equal 1, @object.state_id end end class TransitionWithoutReadingStateTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'idling' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling, false) end def test_should_not_read_from_value_from_object assert_equal 'parked', @transition.from end def test_should_have_to_value assert_equal 'idling', @transition.to end end class TransitionWithActionTest < Test::Unit::TestCase def setup @klass = Class.new do def save end end @machine = StateMachine::Machine.new(@klass, :action => :save) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_have_an_action assert_equal :save, @transition.action end def test_should_not_have_a_result assert_nil @transition.result end end class TransitionAfterBeingPersistedTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :action => :save) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) @transition.persist end def test_should_update_state_value assert_equal 'idling', @object.state end def test_should_not_change_from_state assert_equal 'parked', @transition.from end def test_should_not_change_to_state assert_equal 'idling', @transition.to end def test_should_not_be_able_to_persist_twice @object.state = 'parked' @transition.persist assert_equal 'parked', @object.state end def test_should_be_able_to_persist_again_after_resetting @object.state = 'parked' @transition.reset @transition.persist assert_equal 'idling', @object.state end def test_should_revert_to_from_state_on_rollback @transition.rollback assert_equal 'parked', @object.state end end class TransitionAfterBeingRolledBackTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass, :action => :save) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) @object.state = 'idling' @transition.rollback end def test_should_update_state_value_to_from_state assert_equal 'parked', @object.state end def test_should_not_change_from_state assert_equal 'parked', @transition.from end def test_should_not_change_to_state assert_equal 'idling', @transition.to end def test_should_still_be_able_to_persist @transition.persist assert_equal 'idling', @object.state end end class TransitionWithoutCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_succeed assert_equal true, @transition.run_callbacks end def test_should_succeed_if_after_callbacks_skipped assert_equal true, @transition.run_callbacks(:after => false) end def test_should_call_block_if_provided @transition.run_callbacks { @ran_block = true; {} } assert @ran_block end def test_should_track_block_result @transition.run_callbacks {{:result => 1}} assert_equal 1, @transition.result end end class TransitionWithBeforeCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_run_before_callbacks @machine.before_transition {@run = true} result = @transition.run_callbacks assert_equal true, result assert_equal true, @run end def test_should_only_run_those_that_match_transition_context @count = 0 callback = lambda {@count += 1} @machine.before_transition :from => :parked, :to => :idling, :on => :park, :do => callback @machine.before_transition :from => :parked, :to => :parked, :on => :park, :do => callback @machine.before_transition :from => :parked, :to => :idling, :on => :ignite, :do => callback @machine.before_transition :from => :idling, :to => :idling, :on => :park, :do => callback @transition.run_callbacks assert_equal 1, @count end def test_should_pass_transition_as_argument @machine.before_transition {|*args| @args = args} @transition.run_callbacks assert_equal [@object, @transition], @args end def test_should_catch_halts @machine.before_transition {throw :halt} result = nil assert_nothing_thrown { result = @transition.run_callbacks } assert_equal false, result end def test_should_not_catch_exceptions @machine.before_transition {raise ArgumentError} assert_raise(ArgumentError) { @transition.run_callbacks } end def test_should_not_be_able_to_run_twice @count = 0 @machine.before_transition {@count += 1} @transition.run_callbacks @transition.run_callbacks assert_equal 1, @count end def test_should_be_able_to_run_again_after_halt @count = 0 @machine.before_transition {@count += 1; throw :halt} @transition.run_callbacks @transition.run_callbacks assert_equal 2, @count end def test_should_be_able_to_run_again_after_resetting @count = 0 @machine.before_transition {@count += 1} @transition.run_callbacks @transition.reset @transition.run_callbacks assert_equal 2, @count end def test_should_succeed_if_block_result_is_false @machine.before_transition {@run = true} assert_equal true, @transition.run_callbacks {{:result => false}} assert @run end def test_should_succeed_if_block_result_is_true @machine.before_transition {@run = true} assert_equal true, @transition.run_callbacks {{:result => true}} assert @run end def test_should_succeed_if_block_success_is_false @machine.before_transition {@run = true} assert_equal true, @transition.run_callbacks {{:success => false}} assert @run end def test_should_succeed_if_block_success_is_true @machine.before_transition {@run = true} assert_equal true, @transition.run_callbacks {{:success => true}} assert @run end end class TransitionWithMultipleBeforeCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_run_in_the_order_they_were_defined @callbacks = [] @machine.before_transition {@callbacks << 1} @machine.before_transition {@callbacks << 2} @transition.run_callbacks assert_equal [1, 2], @callbacks end def test_should_not_run_further_callbacks_if_halted @callbacks = [] @machine.before_transition {@callbacks << 1; throw :halt} @machine.before_transition {@callbacks << 2} assert_equal false, @transition.run_callbacks assert_equal [1], @callbacks end def test_should_fail_if_any_callback_halted @machine.before_transition {true} @machine.before_transition {throw :halt} assert_equal false, @transition.run_callbacks end end class TransitionWithAfterCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_run_after_callbacks @machine.after_transition {|object| @run = true} result = @transition.run_callbacks assert_equal true, result assert_equal true, @run end def test_should_only_run_those_that_match_transition_context @count = 0 callback = lambda {@count += 1} @machine.after_transition :from => :parked, :to => :idling, :on => :park, :do => callback @machine.after_transition :from => :parked, :to => :parked, :on => :park, :do => callback @machine.after_transition :from => :parked, :to => :idling, :on => :ignite, :do => callback @machine.after_transition :from => :idling, :to => :idling, :on => :park, :do => callback @transition.run_callbacks assert_equal 1, @count end def test_should_not_run_if_not_successful @run = false @machine.after_transition {|object| @run = true} @transition.run_callbacks {{:success => false}} assert !@run end def test_should_run_if_successful @machine.after_transition {|object| @run = true} @transition.run_callbacks {{:success => true}} assert @run end def test_should_pass_transition_as_argument @machine.after_transition {|*args| @args = args} @transition.run_callbacks assert_equal [@object, @transition], @args end def test_should_catch_halts @machine.after_transition {throw :halt} result = nil assert_nothing_thrown { result = @transition.run_callbacks } assert_equal true, result end def test_should_not_catch_exceptions @machine.after_transition {raise ArgumentError} assert_raise(ArgumentError) { @transition.run_callbacks } end def test_should_not_be_able_to_run_twice @count = 0 @machine.after_transition {@count += 1} @transition.run_callbacks @transition.run_callbacks assert_equal 1, @count end def test_should_not_be_able_to_run_twice_if_halted @count = 0 @machine.after_transition {@count += 1; throw :halt} @transition.run_callbacks @transition.run_callbacks assert_equal 1, @count end def test_should_be_able_to_run_again_after_resetting @count = 0 @machine.after_transition {@count += 1} @transition.run_callbacks @transition.reset @transition.run_callbacks assert_equal 2, @count end end class TransitionWithMultipleAfterCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_run_in_the_order_they_were_defined @callbacks = [] @machine.after_transition {@callbacks << 1} @machine.after_transition {@callbacks << 2} @transition.run_callbacks assert_equal [1, 2], @callbacks end def test_should_not_run_further_callbacks_if_halted @callbacks = [] @machine.after_transition {@callbacks << 1; throw :halt} @machine.after_transition {@callbacks << 2} assert_equal true, @transition.run_callbacks assert_equal [1], @callbacks end def test_should_fail_if_any_callback_halted @machine.after_transition {true} @machine.after_transition {throw :halt} assert_equal true, @transition.run_callbacks end end class TransitionWithAroundCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_run_around_callbacks @machine.around_transition {|object, transition, block| @run_before = true; block.call; @run_after = true} result = @transition.run_callbacks assert_equal true, result assert_equal true, @run_before assert_equal true, @run_after end def test_should_only_run_those_that_match_transition_context @count = 0 callback = lambda {|object, transition, block| @count += 1; block.call} @machine.around_transition :from => :parked, :to => :idling, :on => :park, :do => callback @machine.around_transition :from => :parked, :to => :parked, :on => :park, :do => callback @machine.around_transition :from => :parked, :to => :idling, :on => :ignite, :do => callback @machine.around_transition :from => :idling, :to => :idling, :on => :park, :do => callback @transition.run_callbacks assert_equal 1, @count end def test_should_pass_transition_as_argument @machine.around_transition {|*args| block = args.pop; @args = args; block.call} @transition.run_callbacks assert_equal [@object, @transition], @args end def test_should_run_block_between_callback @callbacks = [] @machine.around_transition {|block| @callbacks << :before; block.call; @callbacks << :after} @transition.run_callbacks { @callbacks << :within; {:success => true} } assert_equal [:before, :within, :after], @callbacks end def test_should_have_access_to_result_after_yield @machine.around_transition {|block| @before_result = @transition.result; block.call; @after_result = @transition.result} @transition.run_callbacks {{:result => 1, :success => true}} assert_nil @before_result assert_equal 1, @after_result end def test_should_catch_before_yield_halts @machine.around_transition {throw :halt} result = nil assert_nothing_thrown { result = @transition.run_callbacks } assert_equal false, result end def test_should_catch_after_yield_halts @machine.around_transition {|block| block.call; throw :halt} result = nil assert_nothing_thrown { result = @transition.run_callbacks } assert_equal true, result end def test_should_not_catch_before_yield @machine.around_transition {raise ArgumentError} assert_raise(ArgumentError) { @transition.run_callbacks } end def test_should_not_catch_after_yield @machine.around_transition {|block| block.call; raise ArgumentError} assert_raise(ArgumentError) { @transition.run_callbacks } end def test_should_fail_if_not_yielded @machine.around_transition {} result = nil assert_nothing_thrown { result = @transition.run_callbacks } assert_equal false, result end def test_should_not_be_able_to_run_twice @before_count = 0 @after_count = 0 @machine.around_transition {|block| @before_count += 1; block.call; @after_count += 1} @transition.run_callbacks @transition.run_callbacks assert_equal 1, @before_count assert_equal 1, @after_count end def test_should_be_able_to_run_again_after_resetting @before_count = 0 @after_count = 0 @machine.around_transition {|block| @before_count += 1; block.call; @after_count += 1} @transition.run_callbacks @transition.reset @transition.run_callbacks assert_equal 2, @before_count assert_equal 2, @after_count end def test_should_succeed_if_block_result_is_false @machine.around_transition {|block| @before_run = true; block.call; @after_run = true} assert_equal true, @transition.run_callbacks {{:success => true, :result => false}} assert @before_run assert @after_run end def test_should_succeed_if_block_result_is_true @machine.around_transition {|block| @before_run = true; block.call; @after_run = true} assert_equal true, @transition.run_callbacks {{:success => true, :result => true}} assert @before_run assert @after_run end def test_should_only_run_before_if_block_success_is_false @after_run = false @machine.around_transition {|block| @before_run = true; block.call; @after_run = true} assert_equal true, @transition.run_callbacks {{:success => false}} assert @before_run assert !@after_run end def test_should_succeed_if_block_success_is_false @machine.around_transition {|block| @before_run = true; block.call; @after_run = true} assert_equal true, @transition.run_callbacks {{:success => true}} assert @before_run assert @after_run end end class TransitionWithMultipleAroundCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_before_yield_in_the_order_they_were_defined @callbacks = [] @machine.around_transition {|block| @callbacks << 1; block.call} @machine.around_transition {|block| @callbacks << 2; block.call} @transition.run_callbacks assert_equal [1, 2], @callbacks end def test_should_before_yield_multiple_methods_in_the_order_they_were_defined @callbacks = [] @machine.around_transition(lambda {|block| @callbacks << 1; block.call}, lambda {|block| @callbacks << 2; block.call}) @machine.around_transition(lambda {|block| @callbacks << 3; block.call}, lambda {|block| @callbacks << 4; block.call}) @transition.run_callbacks assert_equal [1, 2, 3, 4], @callbacks end def test_should_after_yield_in_the_reverse_order_they_were_defined @callbacks = [] @machine.around_transition {|block| block.call; @callbacks << 1} @machine.around_transition {|block| block.call; @callbacks << 2} @transition.run_callbacks assert_equal [2, 1], @callbacks end def test_should_after_yield_multiple_methods_in_the_reverse_order_they_were_defined @callbacks = [] @machine.around_transition(lambda {|block| block.call; @callbacks << 1}) {|block| block.call; @callbacks << 2} @machine.around_transition(lambda {|block| block.call; @callbacks << 3}) {|block| block.call; @callbacks << 4} @transition.run_callbacks assert_equal [4, 3, 2, 1], @callbacks end def test_should_run_block_between_callback @callbacks = [] @machine.around_transition {|block| @callbacks << :before_1; block.call; @callbacks << :after_1} @machine.around_transition {|block| @callbacks << :before_2; block.call; @callbacks << :after_2} @transition.run_callbacks { @callbacks << :within; {:success => true} } assert_equal [:before_1, :before_2, :within, :after_2, :after_1], @callbacks end def test_should_have_access_to_result_after_yield @machine.around_transition {|block| @before_result_1 = @transition.result; block.call; @after_result_1 = @transition.result} @machine.around_transition {|block| @before_result_2 = @transition.result; block.call; @after_result_2 = @transition.result} @transition.run_callbacks {{:result => 1, :success => true}} assert_nil @before_result_1 assert_nil @before_result_2 assert_equal 1, @after_result_1 assert_equal 1, @after_result_2 end def test_should_fail_if_any_before_yield_halted @machine.around_transition {|block| block.call} @machine.around_transition {throw :halt} assert_equal false, @transition.run_callbacks end def test_should_not_continue_around_callbacks_if_before_yield_halted @callbacks = [] @machine.around_transition {@callbacks << 1; throw :halt} @machine.around_transition {|block| @callbacks << 2; block.call; @callbacks << 3} assert_equal false, @transition.run_callbacks assert_equal [1], @callbacks end def test_should_not_continue_around_callbacks_if_later_before_yield_halted @callbacks = [] @machine.around_transition {|block| block.call; @callbacks << 1} @machine.around_transition {throw :halt} @transition.run_callbacks assert_equal [], @callbacks end def test_should_not_run_further_callbacks_if_after_yield_halted @callbacks = [] @machine.around_transition {|block| block.call; @callbacks << 1} @machine.around_transition {|block| block.call; throw :halt} assert_equal true, @transition.run_callbacks assert_equal [], @callbacks end def test_should_fail_if_any_fail_to_yield @callbacks = [] @machine.around_transition {@callbacks << 1} @machine.around_transition {|block| @callbacks << 2; block.call; @callbacks << 3} assert_equal false, @transition.run_callbacks assert_equal [1], @callbacks end end class TransitionWithFailureCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_only_run_those_that_match_transition_context @count = 0 callback = lambda {@count += 1} @machine.after_failure :do => callback @machine.after_failure :on => :park, :do => callback @machine.after_failure :on => :ignite, :do => callback @transition.run_callbacks {{:success => false}} assert_equal 2, @count end def test_should_run_if_not_successful @machine.after_failure {|object| @run = true} @transition.run_callbacks {{:success => false}} assert @run end def test_should_not_run_if_successful @run = false @machine.after_failure {|object| @run = true} @transition.run_callbacks {{:success => true}} assert !@run end def test_should_pass_transition_as_argument @machine.after_failure {|*args| @args = args} @transition.run_callbacks {{:success => false}} assert_equal [@object, @transition], @args end def test_should_catch_halts @machine.after_failure {throw :halt} result = nil assert_nothing_thrown { result = @transition.run_callbacks {{:success => false}} } assert_equal true, result end def test_should_not_catch_exceptions @machine.after_failure {raise ArgumentError} assert_raise(ArgumentError) { @transition.run_callbacks {{:success => false}} } end def test_should_not_be_able_to_run_twice @count = 0 @machine.after_failure {@count += 1} @transition.run_callbacks {{:success => false}} @transition.run_callbacks {{:success => false}} assert_equal 1, @count end def test_should_not_be_able_to_run_twice_if_halted @count = 0 @machine.after_failure {@count += 1; throw :halt} @transition.run_callbacks {{:success => false}} @transition.run_callbacks {{:success => false}} assert_equal 1, @count end def test_should_be_able_to_run_again_after_resetting @count = 0 @machine.after_failure {@count += 1} @transition.run_callbacks {{:success => false}} @transition.reset @transition.run_callbacks {{:success => false}} assert_equal 2, @count end end class TransitionWithMultipleFailureCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_run_in_the_order_they_were_defined @callbacks = [] @machine.after_failure {@callbacks << 1} @machine.after_failure {@callbacks << 2} @transition.run_callbacks {{:success => false}} assert_equal [1, 2], @callbacks end def test_should_not_run_further_callbacks_if_halted @callbacks = [] @machine.after_failure {@callbacks << 1; throw :halt} @machine.after_failure {@callbacks << 2} assert_equal true, @transition.run_callbacks {{:success => false}} assert_equal [1], @callbacks end def test_should_fail_if_any_callback_halted @machine.after_failure {true} @machine.after_failure {throw :halt} assert_equal true, @transition.run_callbacks {{:success => false}} end end class TransitionWithMixedCallbacksTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_before_and_around_callbacks_in_order_defined @callbacks = [] @machine.before_transition {@callbacks << :before_1} @machine.around_transition {|block| @callbacks << :around; block.call} @machine.before_transition {@callbacks << :before_2} assert_equal true, @transition.run_callbacks assert_equal [:before_1, :around, :before_2], @callbacks end def test_should_run_around_callbacks_before_after_callbacks @callbacks = [] @machine.after_transition {@callbacks << :after_1} @machine.around_transition {|block| block.call; @callbacks << :after_2} @machine.after_transition {@callbacks << :after_3} assert_equal true, @transition.run_callbacks assert_equal [:after_2, :after_1, :after_3], @callbacks end def test_should_have_access_to_result_for_both_after_and_around_callbacks @machine.after_transition {@after_result = @transition.result} @machine.around_transition {|block| block.call; @around_result = @transition.result} @transition.run_callbacks {{:result => 1, :success => true}} assert_equal 1, @after_result assert_equal 1, @around_result end def test_should_not_run_further_callbacks_if_before_callback_halts @callbacks = [] @machine.before_transition {@callbacks << :before_1} @machine.around_transition {|block| @callbacks << :before_around_1; block.call; @callbacks << :after_around_1} @machine.before_transition {@callbacks << :before_2; throw :halt} @machine.around_transition {|block| @callbacks << :before_around_2; block.call; @callbacks << :after_around_2} @machine.after_transition {@callbacks << :after} assert_equal false, @transition.run_callbacks assert_equal [:before_1, :before_around_1, :before_2], @callbacks end def test_should_not_run_further_callbacks_if_before_yield_halts @callbacks = [] @machine.before_transition {@callbacks << :before_1} @machine.around_transition {|block| @callbacks << :before_around_1; throw :halt} @machine.before_transition {@callbacks << :before_2; throw :halt} @machine.around_transition {|block| @callbacks << :before_around_2; block.call; @callbacks << :after_around_2} @machine.after_transition {@callbacks << :after} assert_equal false, @transition.run_callbacks assert_equal [:before_1, :before_around_1], @callbacks end def test_should_not_run_further_callbacks_if_around_callback_fails_to_yield @callbacks = [] @machine.before_transition {@callbacks << :before_1} @machine.around_transition {|block| @callbacks << :before_around_1} @machine.before_transition {@callbacks << :before_2; throw :halt} @machine.around_transition {|block| @callbacks << :before_around_2; block.call; @callbacks << :after_around_2} @machine.after_transition {@callbacks << :after} assert_equal false, @transition.run_callbacks assert_equal [:before_1, :before_around_1], @callbacks end def test_should_not_run_further_callbacks_if_after_yield_halts @callbacks = [] @machine.before_transition {@callbacks << :before_1} @machine.around_transition {|block| @callbacks << :before_around_1; block.call; @callbacks << :after_around_1; throw :halt} @machine.before_transition {@callbacks << :before_2} @machine.around_transition {|block| @callbacks << :before_around_2; block.call; @callbacks << :after_around_2} @machine.after_transition {@callbacks << :after} assert_equal true, @transition.run_callbacks assert_equal [:before_1, :before_around_1, :before_2, :before_around_2, :after_around_2, :after_around_1], @callbacks end def test_should_not_run_further_callbacks_if_after_callback_halts @callbacks = [] @machine.before_transition {@callbacks << :before_1} @machine.around_transition {|block| @callbacks << :before_around_1; block.call; @callbacks << :after_around_1} @machine.before_transition {@callbacks << :before_2} @machine.around_transition {|block| @callbacks << :before_around_2; block.call; @callbacks << :after_around_2} @machine.after_transition {@callbacks << :after_1; throw :halt} @machine.after_transition {@callbacks << :after_2} assert_equal true, @transition.run_callbacks assert_equal [:before_1, :before_around_1, :before_2, :before_around_2, :after_around_2, :after_around_1, :after_1], @callbacks end end class TransitionWithBeforeCallbacksSkippedTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_not_run_before_callbacks @run = false @machine.before_transition {@run = true} assert_equal false, @transition.run_callbacks(:before => false) assert !@run end def test_should_run_failure_callbacks @machine.after_failure {@run = true} assert_equal false, @transition.run_callbacks(:before => false) assert @run end end class TransitionWithAfterCallbacksSkippedTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_run_before_callbacks @machine.before_transition {@run = true} assert_equal true, @transition.run_callbacks(:after => false) assert @run end def test_should_not_run_after_callbacks @run = false @machine.after_transition {@run = true} assert_equal true, @transition.run_callbacks(:after => false) assert !@run end if StateMachine::Transition.pause_supported? def test_should_run_around_callbacks_before_yield @machine.around_transition {|block| @run = true; block.call} assert_equal true, @transition.run_callbacks(:after => false) assert @run end def test_should_not_run_around_callbacks_after_yield @run = false @machine.around_transition {|block| block.call; @run = true} assert_equal true, @transition.run_callbacks(:after => false) assert !@run end def test_should_continue_around_transition_execution_on_second_call @callbacks = [] @machine.around_transition {|block| @callbacks << :before_around_1; block.call; @callbacks << :after_around_1} @machine.around_transition {|block| @callbacks << :before_around_2; block.call; @callbacks << :after_around_2} @machine.after_transition {@callbacks << :after} assert_equal true, @transition.run_callbacks(:after => false) assert_equal [:before_around_1, :before_around_2], @callbacks assert_equal true, @transition.run_callbacks assert_equal [:before_around_1, :before_around_2, :after_around_2, :after_around_1, :after], @callbacks end def test_should_not_run_further_callbacks_if_halted_during_continue_around_transition @callbacks = [] @machine.around_transition {|block| @callbacks << :before_around_1; block.call; @callbacks << :after_around_1} @machine.around_transition {|block| @callbacks << :before_around_2; block.call; @callbacks << :after_around_2; throw :halt} @machine.after_transition {@callbacks << :after} assert_equal true, @transition.run_callbacks(:after => false) assert_equal [:before_around_1, :before_around_2], @callbacks assert_equal true, @transition.run_callbacks assert_equal [:before_around_1, :before_around_2, :after_around_2], @callbacks end def test_should_not_be_able_to_continue_twice @count = 0 @machine.around_transition {|block| block.call; @count += 1} @machine.after_transition {@count += 1} @transition.run_callbacks(:after => false) 2.times do assert_equal true, @transition.run_callbacks assert_equal 2, @count end end def test_should_not_be_able_to_continue_again_after_halted @count = 0 @machine.around_transition {|block| block.call; @count += 1; throw :halt} @machine.after_transition {@count += 1} @transition.run_callbacks(:after => false) 2.times do assert_equal true, @transition.run_callbacks assert_equal 1, @count end end def test_should_have_access_to_result_after_continued @machine.around_transition {|block| @around_before_result = @transition.result; block.call; @around_after_result = @transition.result} @machine.after_transition {@after_result = @transition.result} @transition.run_callbacks(:after => false) @transition.run_callbacks {{:result => 1}} assert_nil @around_before_result assert_equal 1, @around_after_result assert_equal 1, @after_result end def test_should_raise_exceptions_during_around_callbacks_after_yield_in_second_execution @machine.around_transition {|block| block.call; raise ArgumentError} assert_nothing_raised { @transition.run_callbacks(:after => false) } assert_raise(ArgumentError) { @transition.run_callbacks } end else def test_should_raise_exception_on_second_call @callbacks = [] @machine.around_transition {|block| @callbacks << :before_around_1; block.call; @callbacks << :after_around_1} @machine.around_transition {|block| @callbacks << :before_around_2; block.call; @callbacks << :after_around_2} @machine.after_transition {@callbacks << :after} assert_raise(ArgumentError) { @transition.run_callbacks(:after => false) } end end end class TransitionAfterBeingPerformedTest < Test::Unit::TestCase def setup @klass = Class.new do attr_reader :saved, :save_state def save @save_state = state @saved = true 1 end end @machine = StateMachine::Machine.new(@klass, :action => :save) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) @result = @transition.perform end def test_should_have_empty_args assert_equal [], @transition.args end def test_should_have_a_result assert_equal 1, @transition.result end def test_should_be_successful assert_equal true, @result end def test_should_change_the_current_state assert_equal 'idling', @object.state end def test_should_run_the_action assert @object.saved end def test_should_run_the_action_after_saving_the_state assert_equal 'idling', @object.save_state end end class TransitionWithPerformArgumentsTest < Test::Unit::TestCase def setup @klass = Class.new do attr_reader :saved def save @saved = true end end @machine = StateMachine::Machine.new(@klass, :action => :save) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_have_arguments @transition.perform(1, 2) assert_equal [1, 2], @transition.args assert @object.saved end def test_should_not_include_run_action_in_arguments @transition.perform(1, 2, false) assert_equal [1, 2], @transition.args assert !@object.saved end end class TransitionWithoutRunningActionTest < Test::Unit::TestCase def setup @klass = Class.new do attr_reader :saved def save @saved = true end end @machine = StateMachine::Machine.new(@klass, :action => :save) @machine.state :parked, :idling @machine.event :ignite @machine.after_transition {|object| @run_after = true} @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) @result = @transition.perform(false) end def test_should_have_empty_args assert_equal [], @transition.args end def test_should_not_have_a_result assert_nil @transition.result end def test_should_be_successful assert_equal true, @result end def test_should_change_the_current_state assert_equal 'idling', @object.state end def test_should_not_run_the_action assert !@object.saved end def test_should_run_after_callbacks assert @run_after end end class TransitionWithTransactionsTest < Test::Unit::TestCase def setup @klass = Class.new do class << self attr_accessor :running_transaction end attr_accessor :result def save @result = self.class.running_transaction true end end @machine = StateMachine::Machine.new(@klass, :action => :save) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) class << @machine def within_transaction(object) owner_class.running_transaction = object yield owner_class.running_transaction = false end end end def test_should_run_blocks_within_transaction_for_object @transition.within_transaction do @result = @klass.running_transaction end assert_equal @object, @result end end class TransitionTransientTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) @transition.transient = true end def test_should_be_transient assert @transition.transient? end end class TransitionEqualityTest < Test::Unit::TestCase def setup @klass = Class.new @machine = StateMachine::Machine.new(@klass) @machine.state :parked, :idling @machine.event :ignite @object = @klass.new @object.state = 'parked' @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) end def test_should_be_equal_with_same_properties transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling) assert_equal transition, @transition end def test_should_not_be_equal_with_different_machines machine = StateMachine::Machine.new(@klass, :status, :namespace => :other) machine.state :parked, :idling machine.event :ignite transition = StateMachine::Transition.new(@object, machine, :ignite, :parked, :idling) assert_not_equal transition, @transition end def test_should_not_be_equal_with_different_objects transition = StateMachine::Transition.new(@klass.new, @machine, :ignite, :parked, :idling) assert_not_equal transition, @transition end def test_should_not_be_equal_with_different_event_names @machine.event :park transition = StateMachine::Transition.new(@object, @machine, :park, :parked, :idling) assert_not_equal transition, @transition end def test_should_not_be_equal_with_different_from_state_names @machine.state :first_gear transition = StateMachine::Transition.new(@object, @machine, :ignite, :first_gear, :idling) assert_not_equal transition, @transition end def test_should_not_be_equal_with_different_to_state_names @machine.state :first_gear transition = StateMachine::Transition.new(@object, @machine, :ignite, :idling, :first_gear) assert_not_equal transition, @transition end end