state_machine 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.rdoc +26 -0
- data/README.rdoc +254 -46
- data/Rakefile +29 -3
- data/examples/AutoShop_state.png +0 -0
- data/examples/Car_state.jpg +0 -0
- data/examples/Vehicle_state.png +0 -0
- data/lib/state_machine.rb +161 -116
- data/lib/state_machine/assertions.rb +21 -0
- data/lib/state_machine/callback.rb +168 -0
- data/lib/state_machine/eval_helpers.rb +67 -0
- data/lib/state_machine/event.rb +135 -101
- data/lib/state_machine/extensions.rb +83 -0
- data/lib/state_machine/guard.rb +115 -0
- data/lib/state_machine/integrations/active_record.rb +242 -0
- data/lib/state_machine/integrations/data_mapper.rb +198 -0
- data/lib/state_machine/integrations/data_mapper/observer.rb +153 -0
- data/lib/state_machine/integrations/sequel.rb +169 -0
- data/lib/state_machine/machine.rb +746 -352
- data/lib/state_machine/transition.rb +104 -212
- data/test/active_record.log +34865 -0
- data/test/classes/switch.rb +11 -0
- data/test/data_mapper.log +14015 -0
- data/test/functional/state_machine_test.rb +249 -15
- data/test/sequel.log +3835 -0
- data/test/test_helper.rb +3 -12
- data/test/unit/assertions_test.rb +13 -0
- data/test/unit/callback_test.rb +189 -0
- data/test/unit/eval_helpers_test.rb +92 -0
- data/test/unit/event_test.rb +247 -113
- data/test/unit/guard_test.rb +420 -0
- data/test/unit/integrations/active_record_test.rb +515 -0
- data/test/unit/integrations/data_mapper_test.rb +407 -0
- data/test/unit/integrations/sequel_test.rb +244 -0
- data/test/unit/invalid_transition_test.rb +1 -1
- data/test/unit/machine_test.rb +1056 -98
- data/test/unit/state_machine_test.rb +14 -113
- data/test/unit/transition_test.rb +269 -495
- metadata +44 -30
- data/test/app_root/app/models/auto_shop.rb +0 -34
- data/test/app_root/app/models/car.rb +0 -19
- data/test/app_root/app/models/highway.rb +0 -3
- data/test/app_root/app/models/motorcycle.rb +0 -3
- data/test/app_root/app/models/switch.rb +0 -23
- data/test/app_root/app/models/switch_observer.rb +0 -20
- data/test/app_root/app/models/toggle_switch.rb +0 -2
- data/test/app_root/app/models/vehicle.rb +0 -78
- data/test/app_root/config/environment.rb +0 -7
- data/test/app_root/db/migrate/001_create_switches.rb +0 -12
- data/test/app_root/db/migrate/002_create_auto_shops.rb +0 -13
- data/test/app_root/db/migrate/003_create_highways.rb +0 -11
- data/test/app_root/db/migrate/004_create_vehicles.rb +0 -16
- data/test/factory.rb +0 -77
@@ -0,0 +1,420 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
2
|
+
|
3
|
+
class GuardTest < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@guard = StateMachine::Guard.new(:to => 'on', :from => 'off')
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_should_raise_exception_if_invalid_option_specified
|
9
|
+
assert_raise(ArgumentError) { StateMachine::Guard.new(:invalid => true) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_should_have_requirements
|
13
|
+
expected = {:to => 'on', :from => 'off'}
|
14
|
+
assert_equal expected, @guard.requirements
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class GuardWithNoRequirementsTest < Test::Unit::TestCase
|
19
|
+
def setup
|
20
|
+
@object = Object.new
|
21
|
+
@guard = StateMachine::Guard.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_should_match_nil_query
|
25
|
+
assert @guard.matches?(@object, nil)
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_should_match_empty_query
|
29
|
+
assert @guard.matches?(@object, {})
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_should_match_non_empty_query
|
33
|
+
assert @guard.matches?(@object, :from => 'off', :to => 'on', :on => 'turn_on')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class GuardWithToRequirementTest < Test::Unit::TestCase
|
38
|
+
def setup
|
39
|
+
@object = Object.new
|
40
|
+
@guard = StateMachine::Guard.new(:to => 'on')
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_should_match_if_included
|
44
|
+
assert @guard.matches?(@object, :to => 'on')
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_should_not_match_if_not_included
|
48
|
+
assert !@guard.matches?(@object, :to => 'off')
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_should_ignore_from
|
52
|
+
assert @guard.matches?(@object, :to => 'on', :from => 'off')
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_should_ignore_on
|
56
|
+
assert @guard.matches?(@object, :to => 'on', :on => 'turn_on')
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_should_be_included_in_known_states
|
60
|
+
assert_equal %w(on), @guard.known_states
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class GuardWithMultipleToRequirementsTest < Test::Unit::TestCase
|
65
|
+
def setup
|
66
|
+
@object = Object.new
|
67
|
+
@guard = StateMachine::Guard.new(:to => %w(on off))
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_should_match_if_included
|
71
|
+
assert @guard.matches?(@object, :to => 'on')
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_should_not_match_if_not_included
|
75
|
+
assert !@guard.matches?(@object, :to => 'maybe')
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_should_be_included_in_known_states
|
79
|
+
assert_equal %w(on off), @guard.known_states
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class GuardWithFromRequirementTest < Test::Unit::TestCase
|
84
|
+
def setup
|
85
|
+
@object = Object.new
|
86
|
+
@guard = StateMachine::Guard.new(:from => 'on')
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_should_match_if_included
|
90
|
+
assert @guard.matches?(@object, :from => 'on')
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_should_not_match_if_not_included
|
94
|
+
assert !@guard.matches?(@object, :from => 'off')
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_should_ignore_to
|
98
|
+
assert @guard.matches?(@object, :from => 'on', :to => 'off')
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_should_ignore_on
|
102
|
+
assert @guard.matches?(@object, :from => 'on', :on => 'turn_on')
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_should_be_included_in_known_states
|
106
|
+
assert_equal %w(on), @guard.known_states
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class GuardWithMultipleFromRequirementsTest < Test::Unit::TestCase
|
111
|
+
def setup
|
112
|
+
@object = Object.new
|
113
|
+
@guard = StateMachine::Guard.new(:from => %w(on off))
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_should_match_if_included
|
117
|
+
assert @guard.matches?(@object, :from => 'on')
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_should_not_match_if_not_included
|
121
|
+
assert !@guard.matches?(@object, :from => 'maybe')
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_should_be_included_in_known_states
|
125
|
+
assert_equal %w(on off), @guard.known_states
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class GuardWithOnRequirementTest < Test::Unit::TestCase
|
130
|
+
def setup
|
131
|
+
@object = Object.new
|
132
|
+
@guard = StateMachine::Guard.new(:on => 'turn_on')
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_should_match_if_included
|
136
|
+
assert @guard.matches?(@object, :on => 'turn_on')
|
137
|
+
end
|
138
|
+
|
139
|
+
def test_should_not_match_if_not_included
|
140
|
+
assert !@guard.matches?(@object, :on => 'turn_off')
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_should_ignore_to
|
144
|
+
assert @guard.matches?(@object, :on => 'turn_on', :to => 'off')
|
145
|
+
end
|
146
|
+
|
147
|
+
def test_should_ignore_from
|
148
|
+
assert @guard.matches?(@object, :on => 'turn_on', :from => 'off')
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_should_not_be_included_in_known_states
|
152
|
+
assert_equal [], @guard.known_states
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
class GuardWithMultipleOnRequirementsTest < Test::Unit::TestCase
|
157
|
+
def setup
|
158
|
+
@object = Object.new
|
159
|
+
@guard = StateMachine::Guard.new(:on => %w(turn_on turn_off))
|
160
|
+
end
|
161
|
+
|
162
|
+
def test_should_match_if_included
|
163
|
+
assert @guard.matches?(@object, :on => 'turn_on')
|
164
|
+
end
|
165
|
+
|
166
|
+
def test_should_not_match_if_not_included
|
167
|
+
assert !@guard.matches?(@object, :on => 'turn_down')
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
class GuardWithExceptToRequirementTest < Test::Unit::TestCase
|
172
|
+
def setup
|
173
|
+
@object = Object.new
|
174
|
+
@guard = StateMachine::Guard.new(:except_to => 'off')
|
175
|
+
end
|
176
|
+
|
177
|
+
def test_should_match_if_not_included
|
178
|
+
assert @guard.matches?(@object, :to => 'on')
|
179
|
+
end
|
180
|
+
|
181
|
+
def test_should_not_match_if_included
|
182
|
+
assert !@guard.matches?(@object, :to => 'off')
|
183
|
+
end
|
184
|
+
|
185
|
+
def test_should_ignore_from
|
186
|
+
assert @guard.matches?(@object, :except_to => 'off', :from => 'off')
|
187
|
+
end
|
188
|
+
|
189
|
+
def test_should_ignore_on
|
190
|
+
assert @guard.matches?(@object, :except_to => 'off', :on => 'turn_on')
|
191
|
+
end
|
192
|
+
|
193
|
+
def test_should_be_included_in_known_states
|
194
|
+
assert_equal %w(off), @guard.known_states
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
class GuardWithMultipleExceptToRequirementsTest < Test::Unit::TestCase
|
199
|
+
def setup
|
200
|
+
@object = Object.new
|
201
|
+
@guard = StateMachine::Guard.new(:except_to => %w(on off))
|
202
|
+
end
|
203
|
+
|
204
|
+
def test_should_match_if_not_included
|
205
|
+
assert @guard.matches?(@object, :to => 'maybe')
|
206
|
+
end
|
207
|
+
|
208
|
+
def test_should_not_match_if_included
|
209
|
+
assert !@guard.matches?(@object, :to => 'on')
|
210
|
+
end
|
211
|
+
|
212
|
+
def test_should_be_included_in_known_states
|
213
|
+
assert_equal %w(on off), @guard.known_states
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
class GuardWithExceptFromRequirementTest < Test::Unit::TestCase
|
218
|
+
def setup
|
219
|
+
@object = Object.new
|
220
|
+
@guard = StateMachine::Guard.new(:except_from => 'off')
|
221
|
+
end
|
222
|
+
|
223
|
+
def test_should_match_if_not_included
|
224
|
+
assert @guard.matches?(@object, :from => 'on')
|
225
|
+
end
|
226
|
+
|
227
|
+
def test_should_not_match_if_included
|
228
|
+
assert !@guard.matches?(@object, :from => 'off')
|
229
|
+
end
|
230
|
+
|
231
|
+
def test_should_ignore_to
|
232
|
+
assert @guard.matches?(@object, :from => 'on', :to => 'off')
|
233
|
+
end
|
234
|
+
|
235
|
+
def test_should_ignore_on
|
236
|
+
assert @guard.matches?(@object, :from => 'on', :on => 'turn_on')
|
237
|
+
end
|
238
|
+
|
239
|
+
def test_should_be_included_in_known_states
|
240
|
+
assert_equal %w(off), @guard.known_states
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
class GuardWithMultipleExceptFromRequirementsTest < Test::Unit::TestCase
|
245
|
+
def setup
|
246
|
+
@object = Object.new
|
247
|
+
@guard = StateMachine::Guard.new(:except_from => %w(on off))
|
248
|
+
end
|
249
|
+
|
250
|
+
def test_should_match_if_not_included
|
251
|
+
assert @guard.matches?(@object, :from => 'maybe')
|
252
|
+
end
|
253
|
+
|
254
|
+
def test_should_not_match_if_included
|
255
|
+
assert !@guard.matches?(@object, :from => 'on')
|
256
|
+
end
|
257
|
+
|
258
|
+
def test_should_be_included_in_known_states
|
259
|
+
assert_equal %w(on off), @guard.known_states
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
class GuardWithExceptOnRequirementTest < Test::Unit::TestCase
|
264
|
+
def setup
|
265
|
+
@object = Object.new
|
266
|
+
@guard = StateMachine::Guard.new(:except_on => 'turn_off')
|
267
|
+
end
|
268
|
+
|
269
|
+
def test_should_match_if_not_included
|
270
|
+
assert @guard.matches?(@object, :on => 'turn_on')
|
271
|
+
end
|
272
|
+
|
273
|
+
def test_should_not_match_if_included
|
274
|
+
assert !@guard.matches?(@object, :on => 'turn_off')
|
275
|
+
end
|
276
|
+
|
277
|
+
def test_should_ignore_to
|
278
|
+
assert @guard.matches?(@object, :on => 'turn_on', :to => 'off')
|
279
|
+
end
|
280
|
+
|
281
|
+
def test_should_ignore_from
|
282
|
+
assert @guard.matches?(@object, :on => 'turn_on', :from => 'off')
|
283
|
+
end
|
284
|
+
|
285
|
+
def test_should_not_be_included_in_known_states
|
286
|
+
assert_equal [], @guard.known_states
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
class GuardWithMultipleExceptOnRequirementsTest < Test::Unit::TestCase
|
291
|
+
def setup
|
292
|
+
@object = Object.new
|
293
|
+
@guard = StateMachine::Guard.new(:except_on => %w(turn_on turn_off))
|
294
|
+
end
|
295
|
+
|
296
|
+
def test_should_match_if_not_included
|
297
|
+
assert @guard.matches?(@object, :on => 'turn_down')
|
298
|
+
end
|
299
|
+
|
300
|
+
def test_should_not_match_if_included
|
301
|
+
assert !@guard.matches?(@object, :on => 'turn_on')
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
class GuardWithConflictingToRequirementsTest < Test::Unit::TestCase
|
306
|
+
def setup
|
307
|
+
@object = Object.new
|
308
|
+
@guard = StateMachine::Guard.new(:to => 'on', :except_to => 'on')
|
309
|
+
end
|
310
|
+
|
311
|
+
def test_should_ignore_except_requirement
|
312
|
+
assert @guard.matches?(@object, :to => 'on')
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
class GuardWithConflictingFromRequirementsTest < Test::Unit::TestCase
|
317
|
+
def setup
|
318
|
+
@object = Object.new
|
319
|
+
@guard = StateMachine::Guard.new(:from => 'on', :except_from => 'on')
|
320
|
+
end
|
321
|
+
|
322
|
+
def test_should_ignore_except_requirement
|
323
|
+
assert @guard.matches?(@object, :from => 'on')
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
class GuardWithConflictingOnRequirementsTest < Test::Unit::TestCase
|
328
|
+
def setup
|
329
|
+
@object = Object.new
|
330
|
+
@guard = StateMachine::Guard.new(:on => 'turn_on', :except_on => 'turn_on')
|
331
|
+
end
|
332
|
+
|
333
|
+
def test_should_ignore_except_requirement
|
334
|
+
assert @guard.matches?(@object, :on => 'turn_on')
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
class GuardWithDifferentRequirementsTest < Test::Unit::TestCase
|
339
|
+
def setup
|
340
|
+
@object = Object.new
|
341
|
+
@guard = StateMachine::Guard.new(:from => 'off', :to => 'on', :on => 'turn_on')
|
342
|
+
end
|
343
|
+
|
344
|
+
def test_should_match_empty_query
|
345
|
+
assert @guard.matches?(@object)
|
346
|
+
end
|
347
|
+
|
348
|
+
def test_should_match_if_all_requirements_match
|
349
|
+
assert @guard.matches?(@object, :from => 'off', :to => 'on', :on => 'turn_on')
|
350
|
+
end
|
351
|
+
|
352
|
+
def test_should_not_match_if_from_not_included
|
353
|
+
assert !@guard.matches?(@object, :from => 'on')
|
354
|
+
end
|
355
|
+
|
356
|
+
def test_should_not_match_if_to_not_included
|
357
|
+
assert !@guard.matches?(@object, :to => 'off')
|
358
|
+
end
|
359
|
+
|
360
|
+
def test_should_not_match_if_on_not_included
|
361
|
+
assert !@guard.matches?(@object, :on => 'turn_off')
|
362
|
+
end
|
363
|
+
|
364
|
+
def test_should_include_all_known_states
|
365
|
+
assert_equal %w(off on), @guard.known_states.sort
|
366
|
+
end
|
367
|
+
|
368
|
+
def test_should_not_duplicate_known_statse
|
369
|
+
guard = StateMachine::Guard.new(:except_from => 'on', :to => 'on', :on => 'turn_on')
|
370
|
+
assert_equal %w(on), guard.known_states
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
class GuardWithIfConditionalTest < Test::Unit::TestCase
|
375
|
+
def setup
|
376
|
+
@object = Object.new
|
377
|
+
end
|
378
|
+
|
379
|
+
def test_should_match_if_true
|
380
|
+
guard = StateMachine::Guard.new(:if => lambda {true})
|
381
|
+
assert guard.matches?(@object)
|
382
|
+
end
|
383
|
+
|
384
|
+
def test_should_not_match_if_false
|
385
|
+
guard = StateMachine::Guard.new(:if => lambda {false})
|
386
|
+
assert !guard.matches?(@object)
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
class GuardWithUnlessConditionalTest < Test::Unit::TestCase
|
391
|
+
def setup
|
392
|
+
@object = Object.new
|
393
|
+
end
|
394
|
+
|
395
|
+
def test_should_match_if_false
|
396
|
+
guard = StateMachine::Guard.new(:unless => lambda {false})
|
397
|
+
assert guard.matches?(@object)
|
398
|
+
end
|
399
|
+
|
400
|
+
def test_should_not_match_if_true
|
401
|
+
guard = StateMachine::Guard.new(:unless => lambda {true})
|
402
|
+
assert !guard.matches?(@object)
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
class GuardWithConflictingConditionalsTest < Test::Unit::TestCase
|
407
|
+
def setup
|
408
|
+
@object = Object.new
|
409
|
+
end
|
410
|
+
|
411
|
+
def test_should_match_if_true
|
412
|
+
guard = StateMachine::Guard.new(:if => lambda {true}, :unless => lambda {true})
|
413
|
+
assert guard.matches?(@object)
|
414
|
+
end
|
415
|
+
|
416
|
+
def test_should_not_match_if_false
|
417
|
+
guard = StateMachine::Guard.new(:if => lambda {false}, :unless => lambda {false})
|
418
|
+
assert !guard.matches?(@object)
|
419
|
+
end
|
420
|
+
end
|
@@ -0,0 +1,515 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
|
2
|
+
|
3
|
+
begin
|
4
|
+
# Load library
|
5
|
+
require 'rubygems'
|
6
|
+
require 'active_record'
|
7
|
+
|
8
|
+
FIXTURES_ROOT = File.dirname(__FILE__) + '/../../fixtures/'
|
9
|
+
|
10
|
+
# Load TestCase helpers
|
11
|
+
require 'active_support/test_case'
|
12
|
+
require 'active_record/fixtures'
|
13
|
+
require 'active_record/test_case'
|
14
|
+
|
15
|
+
# Set default fixtures configuration
|
16
|
+
ActiveSupport::TestCase.class_eval do
|
17
|
+
self.fixture_path = File.dirname(__FILE__) + '/../../fixtures/'
|
18
|
+
self.use_instantiated_fixtures = false
|
19
|
+
self.use_transactional_fixtures = true
|
20
|
+
end
|
21
|
+
|
22
|
+
# Establish database connection
|
23
|
+
ActiveRecord::Base.establish_connection({'adapter' => 'sqlite3', 'database' => ':memory:'})
|
24
|
+
ActiveRecord::Base.logger = Logger.new("#{File.dirname(__FILE__)}/../../active_record.log")
|
25
|
+
|
26
|
+
# Add model/observer creation helpers
|
27
|
+
ActiveRecord::TestCase.class_eval do
|
28
|
+
# Creates a new ActiveRecord model (and the associated table)
|
29
|
+
def new_model(create_table = true, &block)
|
30
|
+
model = Class.new(ActiveRecord::Base) do
|
31
|
+
connection.create_table(:foo, :force => true) {|t| t.string(:state)} if create_table
|
32
|
+
set_table_name('foo')
|
33
|
+
|
34
|
+
def self.name; 'ActiveRecordTest::Foo'; end
|
35
|
+
end
|
36
|
+
model.class_eval(&block) if block_given?
|
37
|
+
model
|
38
|
+
end
|
39
|
+
|
40
|
+
# Creates a new ActiveRecord observer
|
41
|
+
def new_observer(model, &block)
|
42
|
+
observer = Class.new(ActiveRecord::Observer) do
|
43
|
+
attr_accessor :notifications
|
44
|
+
|
45
|
+
def initialize
|
46
|
+
super
|
47
|
+
@notifications = []
|
48
|
+
end
|
49
|
+
end
|
50
|
+
observer.observe(model)
|
51
|
+
observer.class_eval(&block) if block_given?
|
52
|
+
observer
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
module ActiveRecordTest
|
57
|
+
class IntegrationTest < ActiveRecord::TestCase
|
58
|
+
def test_should_match_if_class_inherits_from_active_record
|
59
|
+
assert StateMachine::Integrations::ActiveRecord.matches?(new_model)
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_should_not_match_if_class_does_not_inherit_from_active_record
|
63
|
+
assert !StateMachine::Integrations::ActiveRecord.matches?(Class.new)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class MachineByDefaultTest < ActiveRecord::TestCase
|
68
|
+
def setup
|
69
|
+
@model = new_model
|
70
|
+
@machine = StateMachine::Machine.new(@model)
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_should_use_save_as_action
|
74
|
+
assert_equal :save, @machine.action
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_should_create_notifier_before_callback
|
78
|
+
assert_equal 1, @machine.callbacks[:before].size
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_should_create_notifier_after_callback
|
82
|
+
assert_equal 1, @machine.callbacks[:after].size
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class MachineTest < ActiveRecord::TestCase
|
87
|
+
def setup
|
88
|
+
@model = new_model
|
89
|
+
@machine = StateMachine::Machine.new(@model)
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_should_create_singular_with_scope
|
93
|
+
assert @model.respond_to?(:with_state)
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_should_only_include_records_with_state_in_singular_with_scope
|
97
|
+
off = @model.create :state => 'off'
|
98
|
+
on = @model.create :state => 'on'
|
99
|
+
|
100
|
+
assert_equal [off], @model.with_state('off')
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_should_create_plural_with_scope
|
104
|
+
assert @model.respond_to?(:with_states)
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_should_only_include_records_with_states_in_plural_with_scope
|
108
|
+
off = @model.create :state => 'off'
|
109
|
+
on = @model.create :state => 'on'
|
110
|
+
|
111
|
+
assert_equal [off, on], @model.with_states('off', 'on')
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_should_create_singular_without_scope
|
115
|
+
assert @model.respond_to?(:without_state)
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_should_only_include_records_without_state_in_singular_without_scope
|
119
|
+
off = @model.create :state => 'off'
|
120
|
+
on = @model.create :state => 'on'
|
121
|
+
|
122
|
+
assert_equal [off], @model.without_state('on')
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_should_create_plural_without_scope
|
126
|
+
assert @model.respond_to?(:without_states)
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_should_only_include_records_without_states_in_plural_without_scope
|
130
|
+
off = @model.create :state => 'off'
|
131
|
+
on = @model.create :state => 'on'
|
132
|
+
error = @model.create :state => 'error'
|
133
|
+
|
134
|
+
assert_equal [off, on], @model.without_states('error')
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_should_rollback_transaction_if_false
|
138
|
+
@machine.within_transaction(@model.new) do
|
139
|
+
@model.create
|
140
|
+
false
|
141
|
+
end
|
142
|
+
|
143
|
+
assert_equal 0, @model.count
|
144
|
+
end
|
145
|
+
|
146
|
+
def test_should_not_rollback_transaction_if_true
|
147
|
+
@machine.within_transaction(@model.new) do
|
148
|
+
@model.create
|
149
|
+
true
|
150
|
+
end
|
151
|
+
|
152
|
+
assert_equal 1, @model.count
|
153
|
+
end
|
154
|
+
|
155
|
+
def test_should_not_override_the_column_reader
|
156
|
+
record = @model.new
|
157
|
+
record[:state] = 'off'
|
158
|
+
assert_equal 'off', record.state
|
159
|
+
end
|
160
|
+
|
161
|
+
def test_should_not_override_the_column_writer
|
162
|
+
record = @model.new
|
163
|
+
record.state = 'off'
|
164
|
+
assert_equal 'off', record[:state]
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
class MachineUnmigratedTest < ActiveRecord::TestCase
|
169
|
+
def setup
|
170
|
+
@model = new_model(false)
|
171
|
+
|
172
|
+
# Drop the table so that it definitely doesn't exist
|
173
|
+
@model.connection.drop_table(:foo) if @model.connection.table_exists?(:foo)
|
174
|
+
end
|
175
|
+
|
176
|
+
def test_should_allow_machine_creation
|
177
|
+
assert_nothing_raised { StateMachine::Machine.new(@model) }
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
class MachineWithInitialStateTest < ActiveRecord::TestCase
|
182
|
+
def setup
|
183
|
+
@model = new_model
|
184
|
+
@machine = StateMachine::Machine.new(@model, :initial => 'off')
|
185
|
+
@record = @model.new
|
186
|
+
end
|
187
|
+
|
188
|
+
def test_should_set_initial_state_on_created_object
|
189
|
+
assert_equal 'off', @record.state
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
class MachineWithNonColumnStateAttributeTest < ActiveRecord::TestCase
|
194
|
+
def setup
|
195
|
+
@model = new_model
|
196
|
+
@machine = StateMachine::Machine.new(@model, :status, :initial => 'off')
|
197
|
+
@record = @model.new
|
198
|
+
end
|
199
|
+
|
200
|
+
def test_should_define_a_reader_attribute_for_the_attribute
|
201
|
+
assert @record.respond_to?(:status)
|
202
|
+
end
|
203
|
+
|
204
|
+
def test_should_define_a_writer_attribute_for_the_attribute
|
205
|
+
assert @record.respond_to?(:status=)
|
206
|
+
end
|
207
|
+
|
208
|
+
def test_should_set_initial_state_on_created_object
|
209
|
+
assert_equal 'off', @record.status
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
class MachineWithCallbacksTest < ActiveRecord::TestCase
|
214
|
+
def setup
|
215
|
+
@model = new_model
|
216
|
+
@machine = StateMachine::Machine.new(@model)
|
217
|
+
@record = @model.new(:state => 'off')
|
218
|
+
@transition = StateMachine::Transition.new(@record, @machine, 'turn_on', 'off', 'on')
|
219
|
+
end
|
220
|
+
|
221
|
+
def test_should_run_before_callbacks
|
222
|
+
called = false
|
223
|
+
@machine.before_transition(lambda {called = true})
|
224
|
+
|
225
|
+
@transition.perform
|
226
|
+
assert called
|
227
|
+
end
|
228
|
+
|
229
|
+
def test_should_pass_record_into_before_callbacks_with_one_argument
|
230
|
+
record = nil
|
231
|
+
@machine.before_transition(lambda {|arg| record = arg})
|
232
|
+
|
233
|
+
@transition.perform
|
234
|
+
assert_equal @record, record
|
235
|
+
end
|
236
|
+
|
237
|
+
def test_should_pass_record_and_transition_into_before_callbacks_with_multiple_arguments
|
238
|
+
callback_args = nil
|
239
|
+
@machine.before_transition(lambda {|*args| callback_args = args})
|
240
|
+
|
241
|
+
@transition.perform
|
242
|
+
assert_equal [@record, @transition], callback_args
|
243
|
+
end
|
244
|
+
|
245
|
+
def test_should_run_before_callbacks_outside_the_context_of_the_record
|
246
|
+
context = nil
|
247
|
+
@machine.before_transition(lambda {context = self})
|
248
|
+
|
249
|
+
@transition.perform
|
250
|
+
assert_equal self, context
|
251
|
+
end
|
252
|
+
|
253
|
+
def test_should_run_after_callbacks
|
254
|
+
called = false
|
255
|
+
@machine.after_transition(lambda {called = true})
|
256
|
+
|
257
|
+
@transition.perform
|
258
|
+
assert called
|
259
|
+
end
|
260
|
+
|
261
|
+
def test_should_pass_record_into_after_callbacks_with_one_argument
|
262
|
+
record = nil
|
263
|
+
@machine.after_transition(lambda {|arg| record = arg})
|
264
|
+
|
265
|
+
@transition.perform
|
266
|
+
assert_equal @record, record
|
267
|
+
end
|
268
|
+
|
269
|
+
def test_should_pass_record_transition_and_result_into_after_callbacks_with_multiple_arguments
|
270
|
+
callback_args = nil
|
271
|
+
@machine.after_transition(lambda {|*args| callback_args = args})
|
272
|
+
|
273
|
+
@transition.perform
|
274
|
+
assert_equal [@record, @transition, true], callback_args
|
275
|
+
end
|
276
|
+
|
277
|
+
def test_should_run_after_callbacks_outside_the_context_of_the_record
|
278
|
+
context = nil
|
279
|
+
@machine.after_transition(lambda {context = self})
|
280
|
+
|
281
|
+
@transition.perform
|
282
|
+
assert_equal self, context
|
283
|
+
end
|
284
|
+
|
285
|
+
def test_should_include_transition_states_in_known_states
|
286
|
+
@machine.before_transition :to => 'error', :do => lambda {}
|
287
|
+
|
288
|
+
assert_equal %w(error), @machine.states.sort
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
class MachineWithFailedBeforeCallbacksTest < ActiveRecord::TestCase
|
293
|
+
def setup
|
294
|
+
@before_count = 0
|
295
|
+
|
296
|
+
@model = new_model
|
297
|
+
@machine = StateMachine::Machine.new(@model)
|
298
|
+
@machine.before_transition(lambda {@before_count += 1; false})
|
299
|
+
@machine.before_transition(lambda {@before_count += 1})
|
300
|
+
@record = @model.new(:state => 'off')
|
301
|
+
@transition = StateMachine::Transition.new(@record, @machine, 'turn_on', 'off', 'on')
|
302
|
+
@result = @transition.perform
|
303
|
+
end
|
304
|
+
|
305
|
+
def test_should_not_be_successful
|
306
|
+
assert !@result
|
307
|
+
end
|
308
|
+
|
309
|
+
def test_should_not_change_current_state
|
310
|
+
assert_equal 'off', @record.state
|
311
|
+
end
|
312
|
+
|
313
|
+
def test_should_not_run_action
|
314
|
+
assert @record.new_record?
|
315
|
+
end
|
316
|
+
|
317
|
+
def test_should_not_run_further_before_callbacks
|
318
|
+
assert_equal 1, @before_count
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
class MachineWithFailedActionTest < ActiveRecord::TestCase
|
323
|
+
def setup
|
324
|
+
@model = new_model do
|
325
|
+
validates_inclusion_of :state, :in => %w(maybe)
|
326
|
+
end
|
327
|
+
|
328
|
+
@machine = StateMachine::Machine.new(@model)
|
329
|
+
@record = @model.new(:state => 'off')
|
330
|
+
@transition = StateMachine::Transition.new(@record, @machine, 'turn_on', 'off', 'on')
|
331
|
+
@result = @transition.perform
|
332
|
+
end
|
333
|
+
|
334
|
+
def test_should_not_be_successful
|
335
|
+
assert !@result
|
336
|
+
end
|
337
|
+
|
338
|
+
def test_should_change_current_state
|
339
|
+
assert_equal 'on', @record.state
|
340
|
+
end
|
341
|
+
|
342
|
+
def test_should_not_save_record
|
343
|
+
assert @record.new_record?
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
class MachineWithFailedAfterCallbacksTest < ActiveRecord::TestCase
|
348
|
+
def setup
|
349
|
+
@after_count = 0
|
350
|
+
|
351
|
+
@model = new_model
|
352
|
+
@machine = StateMachine::Machine.new(@model)
|
353
|
+
@machine.after_transition(lambda {@after_count += 1; false})
|
354
|
+
@machine.after_transition(lambda {@after_count += 1})
|
355
|
+
@record = @model.new(:state => 'off')
|
356
|
+
@transition = StateMachine::Transition.new(@record, @machine, 'turn_on', 'off', 'on')
|
357
|
+
@result = @transition.perform
|
358
|
+
end
|
359
|
+
|
360
|
+
def test_should_be_successful
|
361
|
+
assert @result
|
362
|
+
end
|
363
|
+
|
364
|
+
def test_should_change_current_state
|
365
|
+
assert_equal 'on', @record.state
|
366
|
+
end
|
367
|
+
|
368
|
+
def test_should_save_record
|
369
|
+
assert !@record.new_record?
|
370
|
+
end
|
371
|
+
|
372
|
+
def test_should_not_run_further_after_callbacks
|
373
|
+
assert_equal 1, @after_count
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
class MachineWithObserversTest < ActiveRecord::TestCase
|
378
|
+
def setup
|
379
|
+
@model = new_model
|
380
|
+
@machine = StateMachine::Machine.new(@model)
|
381
|
+
@record = @model.new(:state => 'off')
|
382
|
+
@transition = StateMachine::Transition.new(@record, @machine, 'turn_on', 'off', 'on')
|
383
|
+
end
|
384
|
+
|
385
|
+
def test_should_call_before_event_method
|
386
|
+
observer = new_observer(@model) do
|
387
|
+
def before_turn_on(*args)
|
388
|
+
notifications << args
|
389
|
+
end
|
390
|
+
end
|
391
|
+
instance = observer.instance
|
392
|
+
|
393
|
+
@transition.perform
|
394
|
+
assert_equal [[@record, @transition]], instance.notifications
|
395
|
+
end
|
396
|
+
|
397
|
+
def test_should_call_before_transition_method
|
398
|
+
observer = new_observer(@model) do
|
399
|
+
def before_transition(*args)
|
400
|
+
notifications << args
|
401
|
+
end
|
402
|
+
end
|
403
|
+
instance = observer.instance
|
404
|
+
|
405
|
+
@transition.perform
|
406
|
+
assert_equal [[@record, @transition]], instance.notifications
|
407
|
+
end
|
408
|
+
|
409
|
+
def test_should_call_after_event_method
|
410
|
+
observer = new_observer(@model) do
|
411
|
+
def after_turn_on(*args)
|
412
|
+
notifications << args
|
413
|
+
end
|
414
|
+
end
|
415
|
+
instance = observer.instance
|
416
|
+
|
417
|
+
@transition.perform
|
418
|
+
assert_equal [[@record, @transition]], instance.notifications
|
419
|
+
end
|
420
|
+
|
421
|
+
def test_should_call_after_transition_method
|
422
|
+
observer = new_observer(@model) do
|
423
|
+
def after_transition(*args)
|
424
|
+
notifications << args
|
425
|
+
end
|
426
|
+
end
|
427
|
+
instance = observer.instance
|
428
|
+
|
429
|
+
@transition.perform
|
430
|
+
assert_equal [[@record, @transition]], instance.notifications
|
431
|
+
end
|
432
|
+
|
433
|
+
def test_should_call_event_method_before_transition_method
|
434
|
+
observer = new_observer(@model) do
|
435
|
+
def before_turn_on(*args)
|
436
|
+
notifications << :before_turn_on
|
437
|
+
end
|
438
|
+
|
439
|
+
def before_transition(*args)
|
440
|
+
notifications << :before_transition
|
441
|
+
end
|
442
|
+
end
|
443
|
+
instance = observer.instance
|
444
|
+
|
445
|
+
@transition.perform
|
446
|
+
assert_equal [:before_turn_on, :before_transition], instance.notifications
|
447
|
+
end
|
448
|
+
|
449
|
+
def test_should_call_methods_outside_the_context_of_the_record
|
450
|
+
observer = new_observer(@model) do
|
451
|
+
def before_turn_on(*args)
|
452
|
+
notifications << self
|
453
|
+
end
|
454
|
+
end
|
455
|
+
instance = observer.instance
|
456
|
+
|
457
|
+
@transition.perform
|
458
|
+
assert_equal [instance], instance.notifications
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
class MachineWithMixedCallbacksTest < ActiveRecord::TestCase
|
463
|
+
def setup
|
464
|
+
@model = new_model
|
465
|
+
@machine = StateMachine::Machine.new(@model)
|
466
|
+
@record = @model.new(:state => 'off')
|
467
|
+
@transition = StateMachine::Transition.new(@record, @machine, 'turn_on', 'off', 'on')
|
468
|
+
|
469
|
+
@notifications = []
|
470
|
+
|
471
|
+
# Create callbacks
|
472
|
+
@machine.before_transition(lambda {@notifications << :callback_before_transition})
|
473
|
+
@machine.after_transition(lambda {@notifications << :callback_after_transition})
|
474
|
+
|
475
|
+
# Create observer callbacks
|
476
|
+
observer = new_observer(@model) do
|
477
|
+
def before_turn_on(*args)
|
478
|
+
notifications << :observer_before_turn_on
|
479
|
+
end
|
480
|
+
|
481
|
+
def before_transition(*args)
|
482
|
+
notifications << :observer_before_transition
|
483
|
+
end
|
484
|
+
|
485
|
+
def after_turn_on(*args)
|
486
|
+
notifications << :observer_after_turn_on
|
487
|
+
end
|
488
|
+
|
489
|
+
def after_transition(*args)
|
490
|
+
notifications << :observer_after_transition
|
491
|
+
end
|
492
|
+
end
|
493
|
+
instance = observer.instance
|
494
|
+
instance.notifications = @notifications
|
495
|
+
|
496
|
+
@transition.perform
|
497
|
+
end
|
498
|
+
|
499
|
+
def test_should_invoke_callbacks_in_specific_order
|
500
|
+
expected = [
|
501
|
+
:callback_before_transition,
|
502
|
+
:observer_before_turn_on,
|
503
|
+
:observer_before_transition,
|
504
|
+
:callback_after_transition,
|
505
|
+
:observer_after_turn_on,
|
506
|
+
:observer_after_transition
|
507
|
+
]
|
508
|
+
|
509
|
+
assert_equal expected, @notifications
|
510
|
+
end
|
511
|
+
end
|
512
|
+
end
|
513
|
+
rescue LoadError
|
514
|
+
$stderr.puts 'Skipping ActiveRecord tests. `gem install active_record` and try again.'
|
515
|
+
end
|