require File.expand_path(File.dirname(__FILE__) + '/../test_helper')

class PathByDefaultTest < Test::Unit::TestCase
  def setup
    @klass = Class.new
    @machine = StateMachine::Machine.new(@klass)
    @object = @klass.new
    
    @path = StateMachine::Path.new(@object, @machine)
  end
  
  def test_should_have_an_object
    assert_equal @object, @path.object
  end
  
  def test_should_have_a_machine
    assert_equal @machine, @path.machine
  end
  
  def test_should_not_have_walked_anywhere
    assert_equal [], @path
  end
  
  def test_should_not_have_a_from_name
    assert_nil @path.from_name
  end
  
  def test_should_have_no_from_states
    assert_equal [], @path.from_states
  end
  
  def test_should_not_have_a_to_name
    assert_nil @path.to_name
  end
  
  def test_should_have_no_to_states
    assert_equal [], @path.to_states
  end
  
  def test_should_have_no_events
    assert_equal [], @path.events
  end
  
  def test_should_not_be_able_to_walk_anywhere
    walked = false
    @path.walk { walked = true }
    assert_equal false, walked
  end
  
  def test_should_not_be_complete
    assert_equal false, @path.complete?
  end
end

class PathTest < Test::Unit::TestCase
  def setup
    @klass = Class.new
    @machine = StateMachine::Machine.new(@klass)
    @object = @klass.new
  end
  
  def test_should_raise_exception_if_invalid_option_specified
    exception = assert_raise(ArgumentError) {StateMachine::Path.new(@object, @machine, :invalid => true)}
    assert_equal 'Invalid key(s): invalid', exception.message
  end
end

class PathWithoutTransitionsTest < Test::Unit::TestCase
  def setup
    @klass = Class.new
    @machine = StateMachine::Machine.new(@klass)
    @machine.state :parked, :idling
    @machine.event :ignite
    
    @object = @klass.new
    
    @path = StateMachine::Path.new(@object, @machine)
    @path.concat([
      @ignite_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
    ])
  end
  
  def test_should_not_be_able_to_walk_anywhere
    walked = false
    @path.walk { walked = true }
    assert_equal false, walked
  end
end

class PathWithTransitionsTest < Test::Unit::TestCase
  def setup
    @klass = Class.new
    @machine = StateMachine::Machine.new(@klass)
    @machine.state :parked, :idling, :first_gear
    @machine.event :ignite, :shift_up
    
    @object = @klass.new
    @object.state = 'parked'
    
    @path = StateMachine::Path.new(@object, @machine)
    @path.concat([
      @ignite_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling),
      @shift_up_transition = StateMachine::Transition.new(@object, @machine, :shift_up, :idling, :first_gear)
    ])
  end
  
  def test_should_enumerate_transitions
    assert_equal [@ignite_transition, @shift_up_transition], @path
  end
  
  def test_should_have_a_from_name
    assert_equal :parked, @path.from_name
  end
  
  def test_should_have_from_states
    assert_equal [:parked, :idling], @path.from_states
  end
  
  def test_should_have_a_to_name
    assert_equal :first_gear, @path.to_name
  end
  
  def test_should_have_to_states
    assert_equal [:idling, :first_gear], @path.to_states
  end
  
  def test_should_have_events
    assert_equal [:ignite, :shift_up], @path.events
  end
  
  def test_should_not_be_able_to_walk_anywhere
    walked = false
    @path.walk { walked = true }
    assert_equal false, walked
  end
  
  def test_should_be_complete
    assert_equal true, @path.complete?
  end
end

class PathWithDuplicatesTest < Test::Unit::TestCase
  def setup
    @klass = Class.new
    @machine = StateMachine::Machine.new(@klass)
    @machine.state :parked, :idling
    @machine.event :park, :ignite
    
    @object = @klass.new
    @object.state = 'parked'
    
    @path = StateMachine::Path.new(@object, @machine)
    @path.concat([
      @ignite_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling),
      @park_transition = StateMachine::Transition.new(@object, @machine, :park, :idling, :parked),
      @ignite_again_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
    ])
  end
  
  def test_should_not_include_duplicates_in_from_states
    assert_equal [:parked, :idling], @path.from_states
  end
  
  def test_should_not_include_duplicates_in_to_states
    assert_equal [:idling, :parked], @path.to_states
  end
  
  def test_should_not_include_duplicates_in_events
    assert_equal [:ignite, :park], @path.events
  end
end

class PathWithAvailableTransitionsTest < Test::Unit::TestCase
  def setup
    @klass = Class.new
    @machine = StateMachine::Machine.new(@klass)
    @machine.state :parked, :idling, :first_gear
    @machine.event :ignite
    @machine.event :shift_up do
      transition :idling => :first_gear
    end
    @machine.event :park do
      transition :idling => :parked
    end
    
    @object = @klass.new
    @object.state = 'parked'
    
    @path = StateMachine::Path.new(@object, @machine)
    @path.concat([
      @ignite_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
    ])
  end
  
  def test_should_not_be_complete
    assert !@path.complete?
  end
  
  def test_should_walk_each_available_transition
    paths = []
    @path.walk {|path| paths << path}
    
    assert_equal [
      [@ignite_transition, StateMachine::Transition.new(@object, @machine, :shift_up, :idling, :first_gear)],
      [@ignite_transition, StateMachine::Transition.new(@object, @machine, :park, :idling, :parked)]
    ], paths
  end
  
  def test_should_yield_path_instances_when_walking
    @path.walk do |path|
      assert_instance_of StateMachine::Path, path
    end
  end
  
  def test_should_not_modify_current_path_after_walking
    @path.walk {}
    assert_equal [@ignite_transition], @path
  end
  
  def test_should_not_modify_object_after_walking
    @path.walk {}
    assert_equal 'parked', @object.state
  end
end

class PathWithGuardedTransitionsTest < Test::Unit::TestCase
  def setup
    @klass = Class.new
    @machine = StateMachine::Machine.new(@klass)
    @machine.state :parked, :idling
    @machine.event :ignite
    @machine.event :shift_up do
      transition :idling => :first_gear, :if => lambda {false}
    end
    
    @object = @klass.new
    @object.state = 'parked'
  end
  
  def test_should_not_walk_transitions_if_guard_enabled
    path = StateMachine::Path.new(@object, @machine)
    path.concat([
      StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
    ])
    
    paths = []
    path.walk {|next_path| paths << next_path}
    
    assert_equal [], paths
  end
  
  def test_should_not_walk_transitions_if_guard_disabled
    path = StateMachine::Path.new(@object, @machine, :guard => false)
    path.concat([
      ignite_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
    ])
    
    paths = []
    path.walk {|next_path| paths << next_path}
    
    assert_equal [
      [ignite_transition, StateMachine::Transition.new(@object, @machine, :shift_up, :idling, :first_gear)]
    ], paths
  end
end

class PathWithEncounteredTransitionsTest < Test::Unit::TestCase
  def setup
    @klass = Class.new
    @machine = StateMachine::Machine.new(@klass)
    @machine.state :parked, :idling, :first_gear
    @machine.event :ignite do
      transition :parked => :idling
    end
    @machine.event :park do
      transition :idling => :parked
    end
    
    @object = @klass.new
    @object.state = 'parked'
    
    @path = StateMachine::Path.new(@object, @machine)
    @path.concat([
      @ignite_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling),
      @park_transition = StateMachine::Transition.new(@object, @machine, :park, :idling, :parked)
    ])
  end
  
  def test_should_be_complete
    assert_equal true, @path.complete?
  end
  
  def test_should_not_be_able_to_walk
    walked = false
    @path.walk { walked = true }
    assert_equal false, walked
  end
end

class PathWithUnreachedTargetTest < Test::Unit::TestCase
  def setup
    @klass = Class.new
    @machine = StateMachine::Machine.new(@klass)
    @machine.state :parked, :idling
    @machine.event :ignite do
      transition :parked => :idling
    end
    
    @object = @klass.new
    @object.state = 'parked'
    
    @path = StateMachine::Path.new(@object, @machine, :target => :parked)
    @path.concat([
      @ignite_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
    ])
  end
  
  def test_should_not_be_complete
    assert_equal false, @path.complete?
  end
  
  def test_should_not_be_able_to_walk
    walked = false
    @path.walk { walked = true }
    assert_equal false, walked
  end
end

class PathWithReachedTargetTest < Test::Unit::TestCase
  def setup
    @klass = Class.new
    @machine = StateMachine::Machine.new(@klass)
    @machine.state :parked, :idling
    @machine.event :ignite do
      transition :parked => :idling
    end
    @machine.event :park do
      transition :idling => :parked
    end
    
    @object = @klass.new
    @object.state = 'parked'
    
    @path = StateMachine::Path.new(@object, @machine, :target => :parked)
    @path.concat([
      @ignite_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling),
      @park_transition = StateMachine::Transition.new(@object, @machine, :park, :idling, :parked)
    ])
  end
  
  def test_should_be_complete
    assert_equal true, @path.complete?
  end
  
  def test_should_not_be_able_to_walk
    walked = false
    @path.walk { walked = true }
    assert_equal false, walked
  end
end

class PathWithAvailableTransitionsAfterReachingTargetTest < Test::Unit::TestCase
  def setup
    @klass = Class.new
    @machine = StateMachine::Machine.new(@klass)
    @machine.state :parked, :idling
    @machine.event :ignite do
      transition :parked => :idling
    end
    @machine.event :shift_up do
      transition :parked => :first_gear
    end
    @machine.event :park do
      transition [:idling, :first_gear] => :parked
    end
    
    @object = @klass.new
    @object.state = 'parked'
    
    @path = StateMachine::Path.new(@object, @machine, :target => :parked)
    @path.concat([
      @ignite_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling),
      @park_transition = StateMachine::Transition.new(@object, @machine, :park, :idling, :parked)
    ])
  end
  
  def test_should_be_complete
    assert_equal true, @path.complete?
  end
  
  def test_should_be_able_to_walk
    paths = []
    @path.walk {|path| paths << path}
    assert_equal [
      [@ignite_transition, @park_transition, StateMachine::Transition.new(@object, @machine, :shift_up, :parked, :first_gear)]
    ], paths
  end
end

class PathWithDeepTargetTest < Test::Unit::TestCase
  def setup
    @klass = Class.new
    @machine = StateMachine::Machine.new(@klass)
    @machine.state :parked, :idling
    @machine.event :ignite do
      transition :parked => :idling
    end
    @machine.event :shift_up do
      transition :parked => :first_gear
    end
    @machine.event :park do
      transition [:idling, :first_gear] => :parked
    end
    
    @object = @klass.new
    @object.state = 'parked'
    
    @path = StateMachine::Path.new(@object, @machine, :target => :parked)
    @path.concat([
      @ignite_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling),
      @park_transition = StateMachine::Transition.new(@object, @machine, :park, :idling, :parked),
      @shift_up_transition = StateMachine::Transition.new(@object, @machine, :shift_up, :parked, :first_gear)
    ])
  end
  
  def test_should_not_be_complete
    assert_equal false, @path.complete?
  end
  
  def test_should_be_able_to_walk
    paths = []
    @path.walk {|path| paths << path}
    assert_equal [
      [@ignite_transition, @park_transition, @shift_up_transition, StateMachine::Transition.new(@object, @machine, :park, :first_gear, :parked)]
    ], paths
  end
end

class PathWithDeepTargetReachedTest < Test::Unit::TestCase
  def setup
    @klass = Class.new
    @machine = StateMachine::Machine.new(@klass)
    @machine.state :parked, :idling
    @machine.event :ignite do
      transition :parked => :idling
    end
    @machine.event :shift_up do
      transition :parked => :first_gear
    end
    @machine.event :park do
      transition [:idling, :first_gear] => :parked
    end
    
    @object = @klass.new
    @object.state = 'parked'
    
    @path = StateMachine::Path.new(@object, @machine, :target => :parked)
    @path.concat([
      @ignite_transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling),
      @park_transition = StateMachine::Transition.new(@object, @machine, :park, :idling, :parked),
      @shift_up_transition = StateMachine::Transition.new(@object, @machine, :shift_up, :parked, :first_gear),
      @park_transition_2 = StateMachine::Transition.new(@object, @machine, :park, :first_gear, :parked)
    ])
  end
  
  def test_should_be_complete
    assert_equal true, @path.complete?
  end
  
  def test_should_not_be_able_to_walk
    walked = false
    @path.walk { walked = true }
    assert_equal false, walked
  end
  
  def test_should_not_be_able_to_walk_with_available_transitions
    @machine.event :park do
      transition :parked => same
    end
    
    walked = false
    @path.walk { walked = true }
    assert_equal false, walked
  end
end
