aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel/test/state_machine_test.rb
blob: 312d8728ba35a83799718f56e9e3a4963d18884f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
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