require 'test_helper'

class StateMachineSubject
  include ActiveModel::StateMachine

  state_machine do
    state :open,   :exit => :exit
    state :closed, :enter => :enter
    
    event :close, :success => :success_callback do
      transitions :to => :closed, :from => [:open]
    end

    event :null do
      transitions :to => :closed, :from => [:open], :guard => :always_false
    end
  end

  state_machine :bar do
    state :read
    state :ended

    event :foo do
      transitions :to => :ended, :from => [:read]
    end
  end

  def always_false
    false
  end
 
  def success_callback
  end
 
  def enter
  end
  def exit
  end
end

class StateMachineSubjectSubclass < StateMachineSubject
end

class StateMachineClassLevelTest < ActiveModel::TestCase
  test 'defines a class level #state_machine method on its including class' do
    assert StateMachineSubject.respond_to?(:state_machine)
  end

  test 'defines a class level #state_machines method on its including class' do
    assert StateMachineSubject.respond_to?(:state_machines)
  end

  test 'class level #state_machine returns machine instance' do
    assert_kind_of ActiveModel::StateMachine::Machine, StateMachineSubject.state_machine
  end

  test 'class level #state_machine returns machine instance with given name' do
    assert_kind_of ActiveModel::StateMachine::Machine, StateMachineSubject.state_machine(:default)
  end

  test 'class level #state_machines returns hash of machine instances' do
    assert_kind_of ActiveModel::StateMachine::Machine, StateMachineSubject.state_machines[:default]
  end

  test "should return a select friendly array of states in the form of [['Friendly name', 'state_name']]" do
    assert_equal [['Open', 'open'], ['Closed', 'closed']], StateMachineSubject.state_machine.states_for_select
  end
end

class StateMachineInstanceLevelTest < ActiveModel::TestCase
  def setup
    @foo = StateMachineSubject.new
  end
 
  test 'defines an accessor for the current state' do
    assert @foo.respond_to?(:current_state)
  end
 
  test 'defines a state querying instance method on including class' do
    assert @foo.respond_to?(:open?)
  end
 
  test 'defines an event! instance method' do
    assert @foo.respond_to?(:close!)
  end
 
  test 'defines an event instance method' do
    assert @foo.respond_to?(:close)
  end
end
 
class StateMachineInitialStatesTest < ActiveModel::TestCase
  def setup
    @foo = StateMachineSubject.new
  end
 
  test 'sets the initial state' do
    assert_equal :open, @foo.current_state
  end
 
  test '#open? should be initially true' do
    assert @foo.open?
  end
  
  test '#closed? should be initially false' do
    assert !@foo.closed?
  end
 
  test 'uses the first state defined if no initial state is given' do
    assert_equal :read, @foo.current_state(:bar)
  end
end
 
class StateMachineEventFiringWithPersistenceTest < ActiveModel::TestCase
  def setup
    @subj = StateMachineSubject.new
  end

  test 'updates the current state' do
    @subj.close!

    assert_equal :closed, @subj.current_state
  end

  test 'fires the Event' do
    @subj.class.state_machine.events[:close].expects(:fire).with(@subj)
    @subj.close!
  end

  test 'calls the success callback if one was provided' do
    @subj.expects(:success_callback)
    @subj.close!
  end

  test 'attempts to persist if write_state is defined' do
    def @subj.write_state
    end

    @subj.expects(:write_state)
    @subj.close!
  end
end

class StateMachineEventFiringWithoutPersistence < ActiveModel::TestCase
  test 'updates the current state' do
    subj = StateMachineSubject.new
    assert_equal :open, subj.current_state
    subj.close
    assert_equal :closed, subj.current_state
  end

  test 'fires the Event' do
    subj = StateMachineSubject.new

    StateMachineSubject.state_machine.events[:close].expects(:fire).with(subj)
    subj.close
  end

  test 'attempts to persist if write_state is defined' do
    subj = StateMachineSubject.new

    def subj.write_state
    end

    subj.expects(:write_state_without_persistence)

    subj.close
  end
end

class StateMachinePersistenceTest < ActiveModel::TestCase
  test 'reads the state if it has not been set and read_state is defined' do
    subj = StateMachineSubject.new
    def subj.read_state
    end

    subj.expects(:read_state).with(StateMachineSubject.state_machine)

    subj.current_state
  end
end

class StateMachineEventCallbacksTest < ActiveModel::TestCase
  test 'should call aasm_event_fired if defined and successful for bang fire' do
    subj = StateMachineSubject.new
    def subj.aasm_event_fired(from, to)
    end

    subj.expects(:event_fired)

    subj.close!
  end

  test 'should call aasm_event_fired if defined and successful for non-bang fire' do
    subj = StateMachineSubject.new
    def subj.aasm_event_fired(from, to)
    end

    subj.expects(:event_fired)

    subj.close
  end

  test 'should call aasm_event_failed if defined and transition failed for bang fire' do
    subj = StateMachineSubject.new
    def subj.event_failed(event)
    end

    subj.expects(:event_failed)

    subj.null!
  end

  test 'should call aasm_event_failed if defined and transition failed for non-bang fire' do
    subj = StateMachineSubject.new
    def subj.aasm_event_failed(event)
    end

    subj.expects(:event_failed)

    subj.null
  end
end

class StateMachineStateActionsTest < ActiveModel::TestCase
  test "calls enter when entering state" do
    subj = StateMachineSubject.new
    subj.expects(:enter)
    subj.close
  end

  test "calls exit when exiting state" do
    subj = StateMachineSubject.new
    subj.expects(:exit)
    subj.close
  end
end

class StateMachineInheritanceTest < ActiveModel::TestCase
  test "has the same states as its parent" do
    assert_equal StateMachineSubject.state_machine.states, StateMachineSubjectSubclass.state_machine.states
  end
 
  test "has the same events as its parent" do
    assert_equal StateMachineSubject.state_machine.events, StateMachineSubjectSubclass.state_machine.events
  end
end

class StateMachineSubject
  state_machine :chetan_patil, :initial => :sleeping do
    state :sleeping
    state :showering
    state :working
    state :dating
 
    event :wakeup do
      transitions :from => :sleeping, :to => [:showering, :working]
    end
 
    event :dress do
      transitions :from => :sleeping, :to => :working, :on_transition => :wear_clothes
      transitions :from => :showering, :to => [:working, :dating], :on_transition => Proc.new { |obj, *args| obj.wear_clothes(*args) }
    end
  end
 
  def wear_clothes(shirt_color, trouser_type)
  end
end

class StateMachineWithComplexTransitionsTest < ActiveModel::TestCase
  def setup
    @subj = StateMachineSubject.new
  end

  test 'transitions to specified next state (sleeping to showering)' do
    @subj.wakeup! :showering
    
    assert_equal :showering, @subj.current_state(:chetan_patil)
  end
 
  test 'transitions to specified next state (sleeping to working)' do
    @subj.wakeup! :working
 
    assert_equal :working, @subj.current_state(:chetan_patil)
  end
 
  test 'transitions to default (first or showering) state' do
    @subj.wakeup!
 
    assert_equal :showering, @subj.current_state(:chetan_patil)
  end
 
  test 'transitions to default state when on_transition invoked' do
    @subj.dress!(nil, 'purple', 'dressy')
 
    assert_equal :working, @subj.current_state(:chetan_patil)
  end

  test 'calls on_transition method with args' do
    @subj.wakeup! :showering

    @subj.expects(:wear_clothes).with('blue', 'jeans')
    @subj.dress! :working, 'blue', 'jeans'
  end

  test 'calls on_transition proc' do
    @subj.wakeup! :showering

    @subj.expects(:wear_clothes).with('purple', 'slacks')
    @subj.dress!(:dating, 'purple', 'slacks')
  end
end
