state-fu 0.11.1

Sign up to get free protection for your applications and to get access to all the features.
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