mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
converted tests for more complex state transitions
This commit is contained in:
parent
74cb056986
commit
a9d9ca16c7
5 changed files with 142 additions and 123 deletions
|
@ -3,9 +3,8 @@ module ActiveModel
|
|||
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)
|
||||
|
@ -19,7 +18,7 @@ module ActiveModel
|
|||
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
|
||||
|
|
|
@ -19,12 +19,21 @@ module ActiveModel
|
|||
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
|
||||
|
@ -37,13 +46,22 @@ module ActiveModel
|
|||
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
|
||||
|
|
|
@ -7,7 +7,7 @@ class EventTest < ActiveModel::TestCase
|
|||
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
|
||||
|
@ -31,7 +31,7 @@ end
|
|||
|
||||
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)
|
||||
|
@ -39,7 +39,7 @@ class EventBeingFiredTest < ActiveModel::TestCase
|
|||
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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -187,47 +187,49 @@ end
|
|||
# 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
|
||||
|
@ -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
|
Loading…
Reference in a new issue