state-fu 0.11.1

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.
Files changed (85) hide show
  1. data/LICENSE +40 -0
  2. data/README.textile +293 -0
  3. data/Rakefile +114 -0
  4. data/lib/binding.rb +292 -0
  5. data/lib/event.rb +192 -0
  6. data/lib/executioner.rb +120 -0
  7. data/lib/hooks.rb +39 -0
  8. data/lib/interface.rb +132 -0
  9. data/lib/lathe.rb +538 -0
  10. data/lib/machine.rb +184 -0
  11. data/lib/method_factory.rb +243 -0
  12. data/lib/persistence.rb +116 -0
  13. data/lib/persistence/active_record.rb +34 -0
  14. data/lib/persistence/attribute.rb +47 -0
  15. data/lib/persistence/base.rb +100 -0
  16. data/lib/persistence/relaxdb.rb +23 -0
  17. data/lib/persistence/session.rb +7 -0
  18. data/lib/sprocket.rb +58 -0
  19. data/lib/state-fu.rb +56 -0
  20. data/lib/state.rb +48 -0
  21. data/lib/support/active_support_lite/array.rb +9 -0
  22. data/lib/support/active_support_lite/array/access.rb +60 -0
  23. data/lib/support/active_support_lite/array/conversions.rb +202 -0
  24. data/lib/support/active_support_lite/array/extract_options.rb +21 -0
  25. data/lib/support/active_support_lite/array/grouping.rb +109 -0
  26. data/lib/support/active_support_lite/array/random_access.rb +13 -0
  27. data/lib/support/active_support_lite/array/wrapper.rb +25 -0
  28. data/lib/support/active_support_lite/blank.rb +67 -0
  29. data/lib/support/active_support_lite/cattr_reader.rb +57 -0
  30. data/lib/support/active_support_lite/keys.rb +57 -0
  31. data/lib/support/active_support_lite/misc.rb +59 -0
  32. data/lib/support/active_support_lite/module.rb +1 -0
  33. data/lib/support/active_support_lite/module/delegation.rb +130 -0
  34. data/lib/support/active_support_lite/object.rb +9 -0
  35. data/lib/support/active_support_lite/string.rb +38 -0
  36. data/lib/support/active_support_lite/symbol.rb +16 -0
  37. data/lib/support/applicable.rb +41 -0
  38. data/lib/support/arrays.rb +197 -0
  39. data/lib/support/core_ext.rb +90 -0
  40. data/lib/support/exceptions.rb +106 -0
  41. data/lib/support/has_options.rb +16 -0
  42. data/lib/support/logger.rb +165 -0
  43. data/lib/support/methodical.rb +17 -0
  44. data/lib/support/no_stdout.rb +55 -0
  45. data/lib/support/plotter.rb +62 -0
  46. data/lib/support/vizier.rb +300 -0
  47. data/lib/tasks/spec_last.rake +55 -0
  48. data/lib/tasks/state_fu.rake +57 -0
  49. data/lib/transition.rb +338 -0
  50. data/lib/transition_query.rb +224 -0
  51. data/spec/custom_formatter.rb +49 -0
  52. data/spec/features/binding_and_transition_helper_mixin_spec.rb +111 -0
  53. data/spec/features/method_missing_only_once_spec.rb +28 -0
  54. data/spec/features/not_requirements_spec.rb +118 -0
  55. data/spec/features/plotter_spec.rb +97 -0
  56. data/spec/features/shared_log_spec.rb +7 -0
  57. data/spec/features/singleton_machine_spec.rb +39 -0
  58. data/spec/features/state_and_array_options_accessor_spec.rb +47 -0
  59. data/spec/features/transition_boolean_comparison_spec.rb +101 -0
  60. data/spec/helper.rb +13 -0
  61. data/spec/integration/active_record_persistence_spec.rb +202 -0
  62. data/spec/integration/binding_extension_spec.rb +41 -0
  63. data/spec/integration/class_accessor_spec.rb +117 -0
  64. data/spec/integration/event_definition_spec.rb +74 -0
  65. data/spec/integration/example_01_document_spec.rb +133 -0
  66. data/spec/integration/example_02_string_spec.rb +88 -0
  67. data/spec/integration/instance_accessor_spec.rb +97 -0
  68. data/spec/integration/lathe_extension_spec.rb +67 -0
  69. data/spec/integration/machine_duplication_spec.rb +101 -0
  70. data/spec/integration/relaxdb_persistence_spec.rb +97 -0
  71. data/spec/integration/requirement_reflection_spec.rb +270 -0
  72. data/spec/integration/state_definition_spec.rb +163 -0
  73. data/spec/integration/transition_spec.rb +1033 -0
  74. data/spec/spec.opts +9 -0
  75. data/spec/spec_helper.rb +132 -0
  76. data/spec/state_fu_spec.rb +948 -0
  77. data/spec/units/binding_spec.rb +192 -0
  78. data/spec/units/event_spec.rb +214 -0
  79. data/spec/units/exceptions_spec.rb +82 -0
  80. data/spec/units/lathe_spec.rb +570 -0
  81. data/spec/units/machine_spec.rb +229 -0
  82. data/spec/units/method_factory_spec.rb +366 -0
  83. data/spec/units/sprocket_spec.rb +69 -0
  84. data/spec/units/state_spec.rb +59 -0
  85. metadata +171 -0
@@ -0,0 +1,570 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
+
3
+ describe StateFu::Lathe do
4
+ include MySpecHelper
5
+
6
+ before do
7
+ reset!
8
+ make_pristine_class('Klass')
9
+ @machine = StateFu::Machine.new()
10
+ @state = Object.new()
11
+ @event = Object.new()
12
+
13
+ stub(@machine).tools() { [].extend( StateFu::ToolArray ) }
14
+ @lathe = StateFu::Lathe.new( @machine )
15
+ @states = [].extend StateFu::StateArray
16
+ stub( @machine ).states() { @states }
17
+ @events = [].extend StateFu::EventArray
18
+ stub( @machine ).events() { @events }
19
+ end
20
+
21
+ describe "constructor" do
22
+ it "should create a new Lathe given valid arguments" do
23
+ lathe = StateFu::Lathe.new( @machine )
24
+ lathe.should be_kind_of( StateFu::Lathe )
25
+ lathe.machine.should == @machine
26
+ lathe.state_or_event.should == nil
27
+ lathe.options.should == {}
28
+ end
29
+
30
+ it "should accept a state_or_event (state / event ) and if given one, be a child" do
31
+ options = {}
32
+ mock( @state ).apply!( options ) {}
33
+ lathe = StateFu::Lathe.new( @machine, @state )
34
+ lathe.should be_kind_of( StateFu::Lathe )
35
+ lathe.machine.should == @machine
36
+ lathe.state_or_event.should == @state
37
+ lathe.options.should == {}
38
+ lathe.should be_child
39
+ end
40
+ end
41
+
42
+ describe "lathe instance with no state_or_event (master lathe for a machine)" do
43
+ before do
44
+ end
45
+
46
+ it "should be master?" do
47
+ @lathe.should be_master
48
+ @lathe.should_not be_child
49
+ end
50
+
51
+ describe "defining a state with .state" do
52
+
53
+ it "should add a state to the lathe's machine.states if the named state does not exist" do
54
+ @lathe.state( :wibble )
55
+ @machine.states.should_not be_empty
56
+ @machine.states.length.should == 1
57
+ s = @machine.states.first
58
+ s.should be_kind_of( StateFu::State )
59
+ s.name.should == :wibble
60
+ end
61
+
62
+ it "should create a child lathe and apply the options and block if supplied" do
63
+ options = {:banana => :flower}
64
+ @state = Object.new()
65
+ @child = Object.new()
66
+ # can't mock the block :(
67
+ mock( StateFu::State ).new( @machine, :wobble, options ) { @state }
68
+ mock( StateFu::Lathe ).new( @machine, @state, options ) { @child }
69
+ mock( @child )
70
+ @lathe.state( :wobble, options )
71
+ end
72
+
73
+ it "should update the named state if it exists" do
74
+ @lathe.state( :wibble, { :nick => :wobble } )
75
+ @machine.states.should_not be_empty
76
+ @machine.states.length.should == 1
77
+ s = @machine.states.first
78
+ @lathe.state( :wibble, { :meta => :voodoo } ).should == s
79
+ s.options[:meta].should == :voodoo
80
+ s.options[:nick].should == :wobble
81
+ end
82
+
83
+ it "should return the named state" do
84
+ s = @lathe.state( :wibble, { :nick => :wobble } )
85
+ s.should be_kind_of( StateFu::State )
86
+ s.name.should == :wibble
87
+ end
88
+ end # .state
89
+
90
+ describe "defining multiple states with .states" do
91
+
92
+ it "should add all states named to the machine if they dont exist" do
93
+ @lathe.states :a, :b, :c, {:group => :alphabet} do
94
+ requires :jackson_five
95
+ end
96
+ @machine.states.length.should == 3
97
+ @machine.states.map(&:name).should == [:a, :b, :c]
98
+ @machine.states.each {|s| s.options[:group].should == :alphabet }
99
+ @machine.states.each {|s| s.entry_requirements.should include(:jackson_five) }
100
+ end
101
+
102
+ it "should apply the block / options to each named state if it already exists" do
103
+ @lathe.state :lemon do
104
+ requires :squinty_face
105
+ end
106
+ @lathe.states :mango, :orange, :lemon, {:group => :fruit } do
107
+ requires :knife
108
+ on_entry :peel
109
+ end
110
+ @lathe.states :orange, :lemon, :mandarin, { :type => :citrus } do
111
+ requires :juicer
112
+ on_entry :juice
113
+ end
114
+ states = @machine.states
115
+ states[:mango ].options.should == { :group => :fruit }
116
+ states[:lemon ].options.should == { :group => :fruit, :type => :citrus }
117
+ states[:mandarin].options.should == { :type => :citrus }
118
+ states[:mango ].entry_requirements.should == [:knife]
119
+ states[:lemon ].entry_requirements.should == [:squinty_face, :knife, :juicer]
120
+ states[:mandarin].entry_requirements.should == [:juicer]
121
+ states[:mango ].hooks[:entry].should == [:peel]
122
+ states[:lemon ].hooks[:entry].should == [:peel, :juice]
123
+ states[:mandarin].hooks[:entry].should == [:juice]
124
+ end
125
+
126
+ it "should apply to all existing states given :ALL" do
127
+ @lathe.states :hot, :cold
128
+ names = []
129
+ @lathe.states :ALL do |s|
130
+ names << s.name
131
+ end
132
+ names.should == [:hot, :cold]
133
+ end
134
+
135
+ it "should apply to all existing states given no arguments" do
136
+ @lathe.states :hot, :cold
137
+ names = []
138
+ @lathe.states do |s|
139
+ names << s.name
140
+ end
141
+ names.should == [:hot, :cold]
142
+ end
143
+
144
+ # TODO
145
+ it "should apply to all existing states except those named given :except => [...]" do
146
+ @lathe.states :hot, :cold, :warm
147
+
148
+ names = []
149
+ @lathe.states :ALL, :except => :warm do |s|
150
+ names << s.name
151
+ end
152
+ names.should == [:hot, :cold]
153
+
154
+ names = []
155
+ @lathe.states :ALL, :except => [:hot, :cold] do |s|
156
+ names << s.name
157
+ end
158
+ names.should == [:warm]
159
+ end
160
+
161
+ it "should return an array of states with extensions" do
162
+ x = @lathe.states :hot, :cold, :warm
163
+ x.should be_kind_of( Array )
164
+ x.length.should == 3
165
+ x.each {|e| e.should be_kind_of( StateFu::State ) }
166
+ x.map(&:name).should == [:hot, :cold, :warm]
167
+ x.except(:warm).map(&:name).should == [:hot, :cold]
168
+ end
169
+ end # states
170
+
171
+ describe "defining an event with .event" do
172
+
173
+ it "should add a event to the lathe's machine.events if the named event does not exist" do
174
+ @lathe.event( :wibble )
175
+ @machine.events.should_not be_empty
176
+ @machine.events.length.should == 1
177
+ s = @machine.events.first
178
+ s.should be_kind_of( StateFu::Event )
179
+ s.name.should == :wibble
180
+ end
181
+
182
+ it "should create a child lathe and apply the options and block if supplied" do
183
+ options = {:banana => :flower}
184
+ @event = Object.new()
185
+ @child = Object.new()
186
+ # can't mock the block :(
187
+ mock( StateFu::Event ).new( @machine, :wobble, options ) { @event }
188
+ mock( StateFu::Lathe ).new( @machine, @event, options ) { @child }
189
+ mock( @child )
190
+ @lathe.event( :wobble, options )
191
+ end
192
+
193
+ it "should update the named event if it exists" do
194
+ @lathe.event( :wibble )
195
+ @machine.events.should_not be_empty
196
+ @machine.events.length.should == 1
197
+ s = @machine.events.first
198
+ @lathe.event( :wibble, { :meta => :voodoo } ).should == s
199
+ s.options[:meta].should == :voodoo
200
+ end
201
+
202
+ it "should create states mentioned in the event definition and add them to machine.states" do
203
+ @machine = StateFu::Machine.new( :snoo )
204
+ @lathe = StateFu::Lathe.new( @machine )
205
+ @lathe.event(:wobble, :from => [:a, :b], :to => :c )
206
+ @machine.events.should_not be_empty
207
+ @machine.events.length.should == 1
208
+ @machine.events.first.name.should == :wobble
209
+ @machine.states.length.should == 3
210
+ @machine.states.map(&:name).sort_by {|x| x.to_s }.should == [ :a, :b, :c]
211
+ @machine.events[:wobble].origins.map(&:name).should == [:a,:b]
212
+ @machine.events[:wobble].targets.map(&:name).should == [:c]
213
+ end
214
+
215
+ it "should allow definition of events using :from => {*origin => *target}" do
216
+ @machine = StateFu::Machine.new( :hash_it_up )
217
+ @lathe = StateFu::Lathe.new( @machine )
218
+ e = @lathe.event(:snooze, :from => { :nine => :ten } )
219
+ e.name.should == :snooze
220
+ e.origins.length.should == 1
221
+ e.origin.name.should == :nine
222
+ e.targets.length.should == 1
223
+ e.target.name.should == :ten
224
+ end
225
+
226
+ end # .event
227
+
228
+ describe "defining multiple events with .events" do
229
+
230
+ it "should add all events named to the machine if they dont exist" do
231
+ @lathe.event :tickle
232
+ @lathe.events :hit, :smack, :punch, {:group => :acts_of_violence} do
233
+ requires :strong_stomach
234
+ end
235
+ e = @machine.events
236
+ e.length.should == 4
237
+ e.map(&:name).should == [:tickle, :hit, :smack, :punch]
238
+ e[:tickle].options[:group].should == nil
239
+ e[:punch ].options[:group].should == :acts_of_violence
240
+ e[:tickle].requirements.should == []
241
+ e[:punch ].requirements.should == [:strong_stomach]
242
+ end
243
+
244
+ it "should apply the block / options to each named event if it already exists" do
245
+ @lathe.event :fart, { :socially_acceptable => false } do
246
+ requires :tilt_to_one_side
247
+ after :inhale_through_nose
248
+ end
249
+
250
+ @lathe.event :smile, { :socially_acceptable => true } do
251
+ requires :teeth
252
+ after :close_mouth
253
+ end
254
+
255
+ @lathe.events :smile, :fart, { :group => :human_actions } do
256
+ requires :corporeal_body, :free_will
257
+ after :blink
258
+ end
259
+ e = @machine.events
260
+ e[:fart].options[:socially_acceptable].should == false
261
+ e[:smile].options[:socially_acceptable].should == true
262
+ e[:fart].requirements.should == [:tilt_to_one_side, :corporeal_body, :free_will]
263
+ e[:smile].requirements.should == [:teeth, :corporeal_body, :free_will]
264
+ e[:fart].hooks[:after].should == [:inhale_through_nose, :blink]
265
+ e[:smile].hooks[:after].should == [:close_mouth, :blink]
266
+ end
267
+
268
+ it "should apply to all existing events given :ALL" do
269
+ @lathe.events :spit, :run
270
+ names = []
271
+ @lathe.events :ALL do |s|
272
+ names << s.name
273
+ end
274
+ names.should == [:spit, :run]
275
+ end
276
+
277
+ it "should apply to all existing events given no arguments" do
278
+ @lathe.events :dance, :juggle
279
+ names = []
280
+ @lathe.events do |s|
281
+ names << s.name
282
+ end
283
+ names.should == [:dance, :juggle]
284
+ end
285
+
286
+ # TODO
287
+ it "should apply to all existing events except those named given :except => [...]" do
288
+ @lathe.events :wink, :bow, :salute
289
+
290
+ names = []
291
+ @lathe.events :ALL, :except => :salute do |s|
292
+ names << s.name
293
+ end
294
+ names.should == [:wink, :bow]
295
+
296
+ names = []
297
+ @lathe.events :ALL, :except => [:bow, :wink] do |s|
298
+ names << s.name
299
+ end
300
+ names.should == [:salute]
301
+
302
+ end
303
+
304
+ end # events
305
+
306
+ describe "initial_state" do
307
+
308
+ it "should set the initial state to its argument, creating if it does not exist" do
309
+ @machine.instance_eval do
310
+ class << self
311
+ attr_accessor :initial_state
312
+ end
313
+ end
314
+ @machine.states.should be_empty
315
+ @lathe.initial_state :bambi
316
+ @machine.states.should_not be_empty
317
+ @machine.states.length.should == 1
318
+ @machine.states.first.name.should == :bambi
319
+ @machine.initial_state.name.should == :bambi
320
+ @lathe.initial_state :thumper
321
+ @machine.states.length.should == 2
322
+ @machine.states.map(&:name).should == [:bambi, :thumper]
323
+ @machine.states.last.name.should == :thumper
324
+ @machine.initial_state.name.should == :thumper
325
+ end
326
+ end
327
+
328
+ describe "helper" do
329
+ it "should call machine.helper *args" do
330
+ mock( @machine ).helper( :fee, :fi, :fo, :fum )
331
+ @lathe.helper( :fee, :fi, :fo, :fum )
332
+ end
333
+ end
334
+
335
+ end # master lathe instance
336
+
337
+ # child lathe - created and yielded within nested blocks in a
338
+ # machine definition
339
+ describe "a child lathe for a state" do
340
+ before do
341
+ @master = @lathe
342
+ @state = @lathe.state(:a)
343
+ @lathe = StateFu::Lathe.new( @machine, @state )
344
+ end
345
+
346
+ describe ".cycle( evt_name )" do
347
+ before do
348
+ @machine = StateFu::Machine.new( :snoo )
349
+ @master = StateFu::Lathe.new( @machine )
350
+ @state = @master.state(:a)
351
+ @lathe = StateFu::Lathe.new( @machine, @state )
352
+ end
353
+
354
+ it "should create a named event from and to the lathe's state_or_event (state)" do
355
+
356
+ @machine.events.should be_empty
357
+ @machine.states.length.should == 1
358
+ @lathe.cycle(:rebirth)
359
+ @machine.events.should_not be_empty
360
+ @machine.states.length.should == 1
361
+ cycle = @machine.events.first
362
+ cycle.should be_kind_of( StateFu::Event )
363
+ cycle.origins.should == [@state]
364
+ cycle.targets.should == [@state]
365
+ end
366
+
367
+ it "should create an event with a default name if given no name" do
368
+ @machine.events.should be_empty
369
+ @machine.states.length.should == 1
370
+ @lathe.cycle
371
+ @machine.events.should_not be_empty
372
+ @machine.states.length.should == 1
373
+ e = @machine.events.first
374
+ e.name.should == :cycle_a
375
+ e.origins.should == [@state]
376
+ e.targets.should == [@state]
377
+ end
378
+
379
+ end
380
+
381
+ describe ".event(:name)" do
382
+ before do
383
+ mock( @machine ).find_or_create_states_by_name( @lathe.state_or_event ).at_least(1) { @lathe.state_or_event }
384
+ end
385
+
386
+ it "should create the named event if it does not exist" do
387
+ @machine.events.should be_empty
388
+ @lathe.event(:poop)
389
+ @machine.events.should_not be_empty
390
+ @machine.events[:poop].should be_kind_of( StateFu::Event )
391
+ end
392
+
393
+ it "should update the named event if it does exist" do
394
+ @lathe.machine.should == @machine
395
+ @lathe.event(:poop)
396
+ @machine.events[:poop].options.should == {}
397
+ @lathe.event(:poop, :lick => :me )
398
+ @machine.events[:poop].options[:lick].should == :me
399
+ end
400
+
401
+ it "should yield a created event given a block with arity 1" do
402
+ @machine.events.length.should == 0
403
+ @lathe.event(:poop) do |e| # yield the event
404
+ e.should be_kind_of( StateFu::Event )
405
+ e.name.should == :poop
406
+ e.options[:called] = true
407
+ end
408
+ @machine.events.length.should == 1
409
+ e = @machine.events[:poop]
410
+ e.options[:called].should == true
411
+ end
412
+
413
+ end
414
+
415
+ describe ".requires()" do
416
+
417
+ before do
418
+ @state.exit_requirements.should == []
419
+ @state.entry_requirements.should == []
420
+ end
421
+
422
+ it "should add :method_name to state.entry_requirements given a name" do
423
+ @lathe.requires( :method_name )
424
+ @state.entry_requirements.should == [:method_name]
425
+ @state.exit_requirements.should == []
426
+ end
427
+
428
+
429
+ it "should add :method_name to state.entry_requirements given a name and :on => :exit" do
430
+ @lathe.requires( :method_name, :on => :exit )
431
+ @state.exit_requirements.should == [:method_name]
432
+ @state.entry_requirements.should == []
433
+ end
434
+
435
+ it "should add :method_name to entry_requirements and exit_requirements given a name and :on => [:entry, :exit]" do
436
+ @lathe.requires( :method_name, :on => [:entry, :exit] )
437
+ @state.exit_requirements.should == [:method_name]
438
+ @state.entry_requirements.should == [:method_name]
439
+ end
440
+
441
+ it "should add multiple method_names if more than one is given" do
442
+ @lathe.requires( :method_one, :method_two )
443
+ @lathe.requires( :method_three, :method_four, :on => [:exit] )
444
+ @state.entry_requirements.should == [:method_one, :method_two]
445
+ @state.exit_requirements.should == [:method_three, :method_four]
446
+ end
447
+
448
+ it "should add to machine.named_procs if a block is given" do
449
+ class << @machine
450
+ attr_accessor :named_procs
451
+ end
452
+ @machine.named_procs = {}
453
+ block = lambda { puts "wee" }
454
+ @machine.named_procs.should == {}
455
+ @lathe.requires( :method_name, :on => [:entry, :exit], &block )
456
+ @state.exit_requirements.should == [:method_name]
457
+ @state.entry_requirements.should == [:method_name]
458
+ @machine.named_procs[:method_name].should == block
459
+ end
460
+
461
+ it "should add a message to machine.requirement_messages if a string is given" do
462
+ class << @machine
463
+ attr_accessor :requirement_messages
464
+ end
465
+ @machine.requirement_messages = {}
466
+ @lathe.requires( :method_one, :message => "Method one says no soup for you!" )
467
+ @machine.should respond_to(:requirement_messages)
468
+ @machine.requirement_messages.keys.should == [:method_one]
469
+ @machine.requirement_messages.values.first.should be_kind_of( String )
470
+ end
471
+
472
+ end
473
+ end # a child lathe for a state
474
+
475
+ describe "a child lathe for an event" do
476
+ before do
477
+ @master = @lathe
478
+ @event = @lathe.event( :go )
479
+ @lathe = StateFu::Lathe.new( @machine, @event )
480
+ stub( @machine ).find_or_create_states_by_name(:a) { [:a] }
481
+ stub( @machine ).find_or_create_states_by_name(:b) { [:b] }
482
+ end
483
+
484
+ describe ".from" do
485
+ it "should create any states mentioned which do not exist" do
486
+ mock( @machine ).find_or_create_states_by_name(:a, :b) { [:a, :b] }
487
+ @lathe.from( :a, :b )
488
+ end
489
+
490
+ it "should set the origins to the result of machine.find_or_create_states_by_name" do
491
+ mock( @machine ).find_or_create_states_by_name(:a, :b) { [:a, :b] }
492
+ @lathe.from( :a, :b )
493
+ @event.origins.should == [:a, :b]
494
+ end
495
+
496
+ it "should accumulate @origins on successive invocations" do
497
+ mock( @machine ).find_or_create_states_by_name(:a, :b) { [:a, :b] }
498
+ mock( @machine ).find_or_create_states_by_name(:x, :y) { [:x, :y] }
499
+ @lathe.from( :a, :b )
500
+ @event.origins.should == [:a, :b]
501
+ @lathe.from( :x, :y )
502
+ @event.origins.should == [:a, :b, :x, :y]
503
+ end
504
+
505
+ it "should set / update both origin and target if a hash is given" do
506
+ mock( @machine ).find_or_create_states_by_name(:a) { [:a] }
507
+ mock( @machine ).find_or_create_states_by_name(:b) { [:b] }
508
+ mock( @machine ).find_or_create_states_by_name(:a, :b) { [:a, :b] }
509
+ mock( @machine ).find_or_create_states_by_name(:x, :y) { [:x, :y] }
510
+ @lathe.from( :a => :b )
511
+ @event.origin.should == :a
512
+ @event.target.should == :b
513
+ @lathe.from( { [:a, :b] => [:x, :y] })
514
+ @event.origin.should == nil
515
+ @event.target.should == nil
516
+ @event.origins.should == [:a, :b]
517
+ @event.targets.should == [:b, :x, :y] # accumulated total
518
+ end
519
+ end
520
+
521
+ describe ".to" do
522
+ it "should create any states mentioned which do not exist" do
523
+ mock( @machine ).find_or_create_states_by_name(:a, :b) { [:a, :b] }
524
+ @lathe.to( :a, :b )
525
+ end
526
+
527
+ it "should set the targets to the result of machine.find_or_create_states_by_name" do
528
+ mock( @machine ).find_or_create_states_by_name(:a, :b) { [:a, :b] }
529
+ @lathe.to( :a, :b )
530
+ @event.targets.should == [:a, :b]
531
+ end
532
+
533
+ it "should update @origins on successive invocations" do
534
+ mock( @machine ).find_or_create_states_by_name(:a, :b) { [:a, :b] }
535
+ mock( @machine ).find_or_create_states_by_name(:x, :y) { [:x, :y] }
536
+ @lathe.to( :a, :b )
537
+ @event.targets.should == [:a, :b]
538
+ @lathe.to( :x, :y )
539
+ @event.targets.should == [:a, :b, :x, :y] # accumulated targets
540
+ end
541
+ end
542
+
543
+ describe ".requires()" do
544
+
545
+ before do
546
+ @event.requirements.should == []
547
+ end
548
+
549
+ it "should add :method_name to event.requirements given a name" do
550
+ @lathe.requires( :method_name )
551
+ @event.requirements.should == [:method_name]
552
+ end
553
+
554
+ it "should add to machine.named_procs if a block is given" do
555
+ class << @machine
556
+ attr_accessor :named_procs
557
+ end
558
+ @machine.named_procs = {}
559
+ block = lambda { puts "wee" }
560
+ @machine.named_procs.should == {}
561
+ @lathe.requires( :method_name, &block )
562
+ @event.requirements.should == [:method_name]
563
+ @machine.named_procs[:method_name].should == block
564
+ end
565
+
566
+ end # requires
567
+
568
+ end # a child lathe for an event
569
+
570
+ end