Skip to content

Commit

Permalink
converted tests for more complex state transitions
Browse files Browse the repository at this point in the history
  • Loading branch information
technoweenie committed Jun 28, 2008
1 parent 74cb056 commit a9d9ca1
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 123 deletions.
7 changes: 3 additions & 4 deletions activemodel/lib/active_model/state_machine/event.rb
Expand Up @@ -3,9 +3,8 @@ module StateMachine
class Event
attr_reader :name, :success

def initialize(name, options = {}, &block)
@name, @transitions = name, []
machine = options.delete(:machine)
def initialize(machine, name, options = {}, &block)
@machine, @name, @transitions = machine, name, []
if machine
machine.klass.send(:define_method, "#{name.to_s}!") do |*args|
machine.fire_event(name, self, true, *args)
Expand All @@ -19,7 +18,7 @@ def initialize(name, options = {}, &block)
end

def fire(obj, to_state = nil, *args)
transitions = @transitions.select { |t| t.from == obj.current_state }
transitions = @transitions.select { |t| t.from == obj.current_state(@machine ? @machine.name : nil) }
raise InvalidTransition if transitions.size == 0

next_state = nil
Expand Down
26 changes: 22 additions & 4 deletions activemodel/lib/active_model/state_machine/machine.rb
Expand Up @@ -19,12 +19,21 @@ def update(options = {}, &block)
self
end

def fire_event(name, record, persist, *args)
state_index[record.current_state].call_action(:exit, record)
if new_state = @events[name].fire(record, *args)
def fire_event(event, record, persist, *args)
state_index[record.current_state(@name)].call_action(:exit, record)
if new_state = @events[event].fire(record, *args)
state_index[new_state].call_action(:enter, record)

if record.respond_to?(event_fired_callback)
record.send(event_fired_callback, record.current_state, new_state)
end

record.current_state(@name, new_state)
else
if record.respond_to?(event_failed_callback)
record.send(event_failed_callback, event)
end

false
end
end
Expand All @@ -37,13 +46,22 @@ def events_for(state)
events = @events.values.select { |event| event.transitions_from_state?(state) }
events.map! { |event| event.name }
end

private
def state(name, options = {})
@states << (state_index[name] ||= State.new(name, :machine => self)).update(options)
end

def event(name, options = {}, &block)
(@events[name] ||= Event.new(name, :machine => self)).update(options, &block)
(@events[name] ||= Event.new(self, name)).update(options, &block)
end

def event_fired_callback
@event_fired_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_fired'
end

def event_failed_callback
@event_failed_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_failed'
end
end
end
Expand Down
6 changes: 3 additions & 3 deletions activemodel/test/state_machine/event_test.rb
Expand Up @@ -7,7 +7,7 @@ def setup
end

def new_event
@event = ActiveModel::StateMachine::Event.new(@name, {:success => @success}) do
@event = ActiveModel::StateMachine::Event.new(nil, @name, {:success => @success}) do
transitions :to => :closed, :from => [:open, :received]
end
end
Expand All @@ -31,15 +31,15 @@ def new_event

class EventBeingFiredTest < ActiveModel::TestCase
test 'should raise an AASM::InvalidTransition error if the transitions are empty' do
event = ActiveModel::StateMachine::Event.new(:event)
event = ActiveModel::StateMachine::Event.new(nil, :event)

assert_raises ActiveModel::StateMachine::InvalidTransition do
event.fire(nil)
end
end

test 'should return the state of the first matching transition it finds' do
event = ActiveModel::StateMachine::Event.new(:event) do
event = ActiveModel::StateMachine::Event.new(nil, :event) do
transitions :to => :closed, :from => [:open, :received]
end

Expand Down
4 changes: 3 additions & 1 deletion activemodel/test/state_machine/machine_test.rb
Expand Up @@ -36,6 +36,8 @@ class StateMachineMachineTest < ActiveModel::TestCase
end

test "finds events for given state" do
assert_equal [:shutdown, :timeout], MachineTestSubject.state_machine.events_for(:open)
events = MachineTestSubject.state_machine.events_for(:open)
assert events.include?(:shutdown)
assert events.include?(:timeout)
end
end
222 changes: 111 additions & 111 deletions activemodel/test/state_machine_test.rb
Expand Up @@ -186,48 +186,50 @@ def subj.aasm_write_state
# foo.aasm_current_state
# end
#end

#describe AASM, '- event callbacks' do
# it 'should call aasm_event_fired if defined and successful for bang fire' do
# foo = Foo.new
# def foo.aasm_event_fired(from, to)
# end
#
# foo.should_receive(:aasm_event_fired)
#
# foo.close!
# end
#
# it 'should call aasm_event_fired if defined and successful for non-bang fire' do
# foo = Foo.new
# def foo.aasm_event_fired(from, to)
# end
#
# foo.should_receive(:aasm_event_fired)
#
# foo.close
# end
#
# it 'should call aasm_event_failed if defined and transition failed for bang fire' do
# foo = Foo.new
# def foo.aasm_event_failed(event)
# end
#
# foo.should_receive(:aasm_event_failed)
#
# foo.null!
# end
#
# it 'should call aasm_event_failed if defined and transition failed for non-bang fire' do
# foo = Foo.new
# def foo.aasm_event_failed(event)
# end
#
# foo.should_receive(:aasm_event_failed)
#
# foo.null
# end
#end

uses_mocha 'StateMachineEventCallbacksTest' do
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
end

uses_mocha 'StateMachineStateActionsTest' do
class StateMachineStateActionsTest < ActiveModel::TestCase
Expand All @@ -254,72 +256,70 @@ class StateMachineInheritanceTest < ActiveModel::TestCase
assert_equal StateMachineSubject.state_machine.events, StateMachineSubjectSubclass.state_machine.events
end
end
#
#
#class ChetanPatil
# include AASM
# aasm_initial_state :sleeping
# aasm_state :sleeping
# aasm_state :showering
# aasm_state :working
# aasm_state :dating
#
# aasm_event :wakeup do
# transitions :from => :sleeping, :to => [:showering, :working]
# end
#
# aasm_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
#
# def wear_clothes(shirt_color, trouser_type)
# end
#end
#
#
#describe ChetanPatil do
# it 'should transition to specified next state (sleeping to showering)' do
# cp = ChetanPatil.new
# cp.wakeup! :showering
#
# cp.aasm_current_state.should == :showering
# end
#
# it 'should transition to specified next state (sleeping to working)' do
# cp = ChetanPatil.new
# cp.wakeup! :working
#
# cp.aasm_current_state.should == :working
# end
#
# it 'should transition to default (first or showering) state' do
# cp = ChetanPatil.new
# cp.wakeup!
#
# cp.aasm_current_state.should == :showering
# end
#
# it 'should transition to default state when on_transition invoked' do
# cp = ChetanPatil.new
# cp.dress!(nil, 'purple', 'dressy')
#
# cp.aasm_current_state.should == :working
# end
#
# it 'should call on_transition method with args' do
# cp = ChetanPatil.new
# cp.wakeup! :showering
#
# cp.should_receive(:wear_clothes).with('blue', 'jeans')
# cp.dress! :working, 'blue', 'jeans'
# end
#
# it 'should call on_transition proc' do
# cp = ChetanPatil.new
# cp.wakeup! :showering
#
# cp.should_receive(:wear_clothes).with('purple', 'slacks')
# cp.dress!(:dating, 'purple', 'slacks')
# 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

uses_mocha "StateMachineWithComplexTransitionsTest on_transition tests" do
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
end

0 comments on commit a9d9ca1

Please sign in to comment.