glebtv_state_machine 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (310) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +2 -0
  5. data/.travis.yml +72 -0
  6. data/.yardopts +5 -0
  7. data/Appraisals +491 -0
  8. data/CHANGELOG.md +502 -0
  9. data/Gemfile +3 -0
  10. data/LICENSE +20 -0
  11. data/README.md +1244 -0
  12. data/Rakefile +42 -0
  13. data/examples/AutoShop_state.png +0 -0
  14. data/examples/Car_state.png +0 -0
  15. data/examples/Gemfile +5 -0
  16. data/examples/Gemfile.lock +14 -0
  17. data/examples/TrafficLight_state.png +0 -0
  18. data/examples/Vehicle_state.png +0 -0
  19. data/examples/auto_shop.rb +13 -0
  20. data/examples/car.rb +21 -0
  21. data/examples/doc/AutoShop.html +2856 -0
  22. data/examples/doc/AutoShop_state.png +0 -0
  23. data/examples/doc/Car.html +919 -0
  24. data/examples/doc/Car_state.png +0 -0
  25. data/examples/doc/TrafficLight.html +2230 -0
  26. data/examples/doc/TrafficLight_state.png +0 -0
  27. data/examples/doc/Vehicle.html +7921 -0
  28. data/examples/doc/Vehicle_state.png +0 -0
  29. data/examples/doc/_index.html +136 -0
  30. data/examples/doc/class_list.html +47 -0
  31. data/examples/doc/css/common.css +1 -0
  32. data/examples/doc/css/full_list.css +55 -0
  33. data/examples/doc/css/style.css +322 -0
  34. data/examples/doc/file_list.html +46 -0
  35. data/examples/doc/frames.html +13 -0
  36. data/examples/doc/index.html +136 -0
  37. data/examples/doc/js/app.js +205 -0
  38. data/examples/doc/js/full_list.js +173 -0
  39. data/examples/doc/js/jquery.js +16 -0
  40. data/examples/doc/method_list.html +734 -0
  41. data/examples/doc/top-level-namespace.html +105 -0
  42. data/examples/merb-rest/controller.rb +51 -0
  43. data/examples/merb-rest/model.rb +28 -0
  44. data/examples/merb-rest/view_edit.html.erb +24 -0
  45. data/examples/merb-rest/view_index.html.erb +23 -0
  46. data/examples/merb-rest/view_new.html.erb +13 -0
  47. data/examples/merb-rest/view_show.html.erb +17 -0
  48. data/examples/rails-rest/controller.rb +43 -0
  49. data/examples/rails-rest/migration.rb +7 -0
  50. data/examples/rails-rest/model.rb +23 -0
  51. data/examples/rails-rest/view__form.html.erb +34 -0
  52. data/examples/rails-rest/view_edit.html.erb +6 -0
  53. data/examples/rails-rest/view_index.html.erb +25 -0
  54. data/examples/rails-rest/view_new.html.erb +5 -0
  55. data/examples/rails-rest/view_show.html.erb +19 -0
  56. data/examples/traffic_light.rb +9 -0
  57. data/examples/vehicle.rb +33 -0
  58. data/gemfiles/active_model_3.0.0.gemfile +7 -0
  59. data/gemfiles/active_model_3.0.0.gemfile.lock +35 -0
  60. data/gemfiles/active_model_3.0.5.gemfile +7 -0
  61. data/gemfiles/active_model_3.0.5.gemfile.lock +35 -0
  62. data/gemfiles/active_model_3.1.1.gemfile +7 -0
  63. data/gemfiles/active_model_3.1.1.gemfile.lock +36 -0
  64. data/gemfiles/active_model_3.2.1.gemfile +7 -0
  65. data/gemfiles/active_model_3.2.12.gemfile +7 -0
  66. data/gemfiles/active_model_3.2.12.gemfile.lock +36 -0
  67. data/gemfiles/active_model_3.2.13.rc1.gemfile +7 -0
  68. data/gemfiles/active_model_3.2.13.rc1.gemfile.lock +36 -0
  69. data/gemfiles/active_model_4.0.0.gemfile +9 -0
  70. data/gemfiles/active_model_4.0.0.gemfile.lock +78 -0
  71. data/gemfiles/active_record_2.0.0.gemfile +9 -0
  72. data/gemfiles/active_record_2.0.0.gemfile.lock +39 -0
  73. data/gemfiles/active_record_2.0.5.gemfile +9 -0
  74. data/gemfiles/active_record_2.0.5.gemfile.lock +39 -0
  75. data/gemfiles/active_record_2.1.0.gemfile +9 -0
  76. data/gemfiles/active_record_2.1.0.gemfile.lock +39 -0
  77. data/gemfiles/active_record_2.1.2.gemfile +9 -0
  78. data/gemfiles/active_record_2.1.2.gemfile.lock +39 -0
  79. data/gemfiles/active_record_2.2.3.gemfile +9 -0
  80. data/gemfiles/active_record_2.2.3.gemfile.lock +39 -0
  81. data/gemfiles/active_record_2.3.12.gemfile +9 -0
  82. data/gemfiles/active_record_2.3.12.gemfile.lock +39 -0
  83. data/gemfiles/active_record_2.3.5.gemfile +9 -0
  84. data/gemfiles/active_record_2.3.5.gemfile.lock +39 -0
  85. data/gemfiles/active_record_3.0.0.gemfile +9 -0
  86. data/gemfiles/active_record_3.0.0.gemfile.lock +51 -0
  87. data/gemfiles/active_record_3.0.5.gemfile +9 -0
  88. data/gemfiles/active_record_3.0.5.gemfile.lock +50 -0
  89. data/gemfiles/active_record_3.1.1.gemfile +9 -0
  90. data/gemfiles/active_record_3.1.1.gemfile.lock +51 -0
  91. data/gemfiles/active_record_3.2.12.gemfile +9 -0
  92. data/gemfiles/active_record_3.2.12.gemfile.lock +51 -0
  93. data/gemfiles/active_record_3.2.13.rc1.gemfile +9 -0
  94. data/gemfiles/active_record_3.2.13.rc1.gemfile.lock +51 -0
  95. data/gemfiles/active_record_4.0.0.gemfile +11 -0
  96. data/gemfiles/active_record_4.0.0.gemfile.lock +83 -0
  97. data/gemfiles/data_mapper_0.10.2.gemfile +13 -0
  98. data/gemfiles/data_mapper_0.10.2.gemfile.lock +56 -0
  99. data/gemfiles/data_mapper_0.9.11.gemfile +13 -0
  100. data/gemfiles/data_mapper_0.9.11.gemfile.lock +71 -0
  101. data/gemfiles/data_mapper_0.9.4.gemfile +12 -0
  102. data/gemfiles/data_mapper_0.9.4.gemfile.lock +70 -0
  103. data/gemfiles/data_mapper_0.9.7.gemfile +13 -0
  104. data/gemfiles/data_mapper_0.9.7.gemfile.lock +67 -0
  105. data/gemfiles/data_mapper_1.0.0.gemfile +12 -0
  106. data/gemfiles/data_mapper_1.0.0.gemfile.lock +63 -0
  107. data/gemfiles/data_mapper_1.0.1.gemfile +12 -0
  108. data/gemfiles/data_mapper_1.0.1.gemfile.lock +63 -0
  109. data/gemfiles/data_mapper_1.0.2.gemfile +12 -0
  110. data/gemfiles/data_mapper_1.0.2.gemfile.lock +63 -0
  111. data/gemfiles/data_mapper_1.1.0.gemfile +12 -0
  112. data/gemfiles/data_mapper_1.1.0.gemfile.lock +61 -0
  113. data/gemfiles/data_mapper_1.2.0.gemfile +12 -0
  114. data/gemfiles/data_mapper_1.2.0.gemfile.lock +61 -0
  115. data/gemfiles/default.gemfile +7 -0
  116. data/gemfiles/default.gemfile.lock +27 -0
  117. data/gemfiles/graphviz_0.9.17.gemfile +7 -0
  118. data/gemfiles/graphviz_0.9.17.gemfile.lock +29 -0
  119. data/gemfiles/graphviz_0.9.21.gemfile +7 -0
  120. data/gemfiles/graphviz_0.9.21.gemfile.lock +29 -0
  121. data/gemfiles/graphviz_1.0.0.gemfile +7 -0
  122. data/gemfiles/graphviz_1.0.0.gemfile.lock +29 -0
  123. data/gemfiles/graphviz_1.0.3.gemfile +7 -0
  124. data/gemfiles/graphviz_1.0.3.gemfile.lock +29 -0
  125. data/gemfiles/graphviz_1.0.8.gemfile +7 -0
  126. data/gemfiles/graphviz_1.0.8.gemfile.lock +29 -0
  127. data/gemfiles/mongo_mapper_0.10.0.gemfile +8 -0
  128. data/gemfiles/mongo_mapper_0.10.0.gemfile.lock +47 -0
  129. data/gemfiles/mongo_mapper_0.11.2.gemfile +9 -0
  130. data/gemfiles/mongo_mapper_0.11.2.gemfile.lock +48 -0
  131. data/gemfiles/mongo_mapper_0.12.0.gemfile +9 -0
  132. data/gemfiles/mongo_mapper_0.12.0.gemfile.lock +48 -0
  133. data/gemfiles/mongo_mapper_0.5.5.gemfile +8 -0
  134. data/gemfiles/mongo_mapper_0.5.5.gemfile.lock +36 -0
  135. data/gemfiles/mongo_mapper_0.5.8.gemfile +8 -0
  136. data/gemfiles/mongo_mapper_0.5.8.gemfile.lock +36 -0
  137. data/gemfiles/mongo_mapper_0.6.0.gemfile +8 -0
  138. data/gemfiles/mongo_mapper_0.6.0.gemfile.lock +36 -0
  139. data/gemfiles/mongo_mapper_0.6.10.gemfile +8 -0
  140. data/gemfiles/mongo_mapper_0.6.10.gemfile.lock +36 -0
  141. data/gemfiles/mongo_mapper_0.7.0.gemfile +8 -0
  142. data/gemfiles/mongo_mapper_0.7.0.gemfile.lock +36 -0
  143. data/gemfiles/mongo_mapper_0.7.5.gemfile +8 -0
  144. data/gemfiles/mongo_mapper_0.7.5.gemfile.lock +39 -0
  145. data/gemfiles/mongo_mapper_0.8.0.gemfile +10 -0
  146. data/gemfiles/mongo_mapper_0.8.0.gemfile.lock +43 -0
  147. data/gemfiles/mongo_mapper_0.8.3.gemfile +10 -0
  148. data/gemfiles/mongo_mapper_0.8.3.gemfile.lock +43 -0
  149. data/gemfiles/mongo_mapper_0.8.4.gemfile +8 -0
  150. data/gemfiles/mongo_mapper_0.8.4.gemfile.lock +42 -0
  151. data/gemfiles/mongo_mapper_0.8.6.gemfile +8 -0
  152. data/gemfiles/mongo_mapper_0.8.6.gemfile.lock +42 -0
  153. data/gemfiles/mongo_mapper_0.9.0.gemfile +7 -0
  154. data/gemfiles/mongo_mapper_0.9.0.gemfile.lock +45 -0
  155. data/gemfiles/mongoid_2.0.0.gemfile +9 -0
  156. data/gemfiles/mongoid_2.0.0.gemfile.lock +49 -0
  157. data/gemfiles/mongoid_2.1.4.gemfile +9 -0
  158. data/gemfiles/mongoid_2.1.4.gemfile.lock +47 -0
  159. data/gemfiles/mongoid_2.2.4.gemfile +9 -0
  160. data/gemfiles/mongoid_2.2.4.gemfile.lock +47 -0
  161. data/gemfiles/mongoid_2.3.3.gemfile +9 -0
  162. data/gemfiles/mongoid_2.3.3.gemfile.lock +47 -0
  163. data/gemfiles/mongoid_2.4.0.gemfile +9 -0
  164. data/gemfiles/mongoid_2.4.0.gemfile.lock +47 -0
  165. data/gemfiles/mongoid_2.4.10.gemfile +9 -0
  166. data/gemfiles/mongoid_2.4.10.gemfile.lock +47 -0
  167. data/gemfiles/mongoid_2.5.2.gemfile +9 -0
  168. data/gemfiles/mongoid_2.5.2.gemfile.lock +47 -0
  169. data/gemfiles/mongoid_2.6.0.gemfile +9 -0
  170. data/gemfiles/mongoid_2.6.0.gemfile.lock +47 -0
  171. data/gemfiles/mongoid_3.0.0.gemfile +8 -0
  172. data/gemfiles/mongoid_3.0.0.gemfile.lock +45 -0
  173. data/gemfiles/mongoid_3.0.22.gemfile +8 -0
  174. data/gemfiles/mongoid_3.0.22.gemfile.lock +45 -0
  175. data/gemfiles/mongoid_3.1.0.gemfile +8 -0
  176. data/gemfiles/mongoid_3.1.0.gemfile.lock +45 -0
  177. data/gemfiles/sequel_2.11.0.gemfile +9 -0
  178. data/gemfiles/sequel_2.11.0.gemfile.lock +33 -0
  179. data/gemfiles/sequel_2.12.0.gemfile +9 -0
  180. data/gemfiles/sequel_2.12.0.gemfile.lock +33 -0
  181. data/gemfiles/sequel_2.8.0.gemfile +9 -0
  182. data/gemfiles/sequel_2.8.0.gemfile.lock +33 -0
  183. data/gemfiles/sequel_3.0.0.gemfile +9 -0
  184. data/gemfiles/sequel_3.0.0.gemfile.lock +33 -0
  185. data/gemfiles/sequel_3.10.0.gemfile +9 -0
  186. data/gemfiles/sequel_3.10.0.gemfile.lock +33 -0
  187. data/gemfiles/sequel_3.13.0.gemfile +9 -0
  188. data/gemfiles/sequel_3.13.0.gemfile.lock +33 -0
  189. data/gemfiles/sequel_3.14.0.gemfile +9 -0
  190. data/gemfiles/sequel_3.14.0.gemfile.lock +33 -0
  191. data/gemfiles/sequel_3.23.0.gemfile +9 -0
  192. data/gemfiles/sequel_3.23.0.gemfile.lock +33 -0
  193. data/gemfiles/sequel_3.24.0.gemfile +9 -0
  194. data/gemfiles/sequel_3.24.0.gemfile.lock +33 -0
  195. data/gemfiles/sequel_3.29.0.gemfile +9 -0
  196. data/gemfiles/sequel_3.29.0.gemfile.lock +33 -0
  197. data/gemfiles/sequel_3.34.0.gemfile +9 -0
  198. data/gemfiles/sequel_3.34.0.gemfile.lock +33 -0
  199. data/gemfiles/sequel_3.35.0.gemfile +9 -0
  200. data/gemfiles/sequel_3.35.0.gemfile.lock +33 -0
  201. data/gemfiles/sequel_3.4.0.gemfile +9 -0
  202. data/gemfiles/sequel_3.4.0.gemfile.lock +33 -0
  203. data/gemfiles/sequel_3.44.0.gemfile +9 -0
  204. data/gemfiles/sequel_3.44.0.gemfile.lock +33 -0
  205. data/glebtv_state_machine.gemspec +22 -0
  206. data/init.rb +1 -0
  207. data/lib/glebtv_state_machine.rb +1 -0
  208. data/lib/state_machine.rb +8 -0
  209. data/lib/state_machine/assertions.rb +36 -0
  210. data/lib/state_machine/branch.rb +225 -0
  211. data/lib/state_machine/callback.rb +236 -0
  212. data/lib/state_machine/core.rb +12 -0
  213. data/lib/state_machine/core_ext.rb +2 -0
  214. data/lib/state_machine/core_ext/class/state_machine.rb +5 -0
  215. data/lib/state_machine/error.rb +13 -0
  216. data/lib/state_machine/eval_helpers.rb +87 -0
  217. data/lib/state_machine/event.rb +257 -0
  218. data/lib/state_machine/event_collection.rb +141 -0
  219. data/lib/state_machine/extensions.rb +149 -0
  220. data/lib/state_machine/graph.rb +92 -0
  221. data/lib/state_machine/helper_module.rb +17 -0
  222. data/lib/state_machine/initializers.rb +4 -0
  223. data/lib/state_machine/initializers/merb.rb +1 -0
  224. data/lib/state_machine/initializers/rails.rb +25 -0
  225. data/lib/state_machine/integrations.rb +121 -0
  226. data/lib/state_machine/integrations/active_model.rb +585 -0
  227. data/lib/state_machine/integrations/active_model/locale.rb +11 -0
  228. data/lib/state_machine/integrations/active_model/observer.rb +33 -0
  229. data/lib/state_machine/integrations/active_model/observer_update.rb +42 -0
  230. data/lib/state_machine/integrations/active_model/versions.rb +31 -0
  231. data/lib/state_machine/integrations/active_record.rb +548 -0
  232. data/lib/state_machine/integrations/active_record/locale.rb +20 -0
  233. data/lib/state_machine/integrations/active_record/versions.rb +123 -0
  234. data/lib/state_machine/integrations/base.rb +100 -0
  235. data/lib/state_machine/integrations/data_mapper.rb +511 -0
  236. data/lib/state_machine/integrations/data_mapper/observer.rb +210 -0
  237. data/lib/state_machine/integrations/data_mapper/versions.rb +85 -0
  238. data/lib/state_machine/integrations/mongo_mapper.rb +389 -0
  239. data/lib/state_machine/integrations/mongo_mapper/locale.rb +4 -0
  240. data/lib/state_machine/integrations/mongo_mapper/versions.rb +89 -0
  241. data/lib/state_machine/integrations/mongoid.rb +465 -0
  242. data/lib/state_machine/integrations/mongoid/locale.rb +4 -0
  243. data/lib/state_machine/integrations/mongoid/versions.rb +81 -0
  244. data/lib/state_machine/integrations/sequel.rb +486 -0
  245. data/lib/state_machine/integrations/sequel/versions.rb +95 -0
  246. data/lib/state_machine/machine.rb +2292 -0
  247. data/lib/state_machine/machine_collection.rb +86 -0
  248. data/lib/state_machine/macro_methods.rb +522 -0
  249. data/lib/state_machine/matcher.rb +123 -0
  250. data/lib/state_machine/matcher_helpers.rb +54 -0
  251. data/lib/state_machine/node_collection.rb +222 -0
  252. data/lib/state_machine/path.rb +120 -0
  253. data/lib/state_machine/path_collection.rb +90 -0
  254. data/lib/state_machine/state.rb +297 -0
  255. data/lib/state_machine/state_collection.rb +112 -0
  256. data/lib/state_machine/state_context.rb +138 -0
  257. data/lib/state_machine/transition.rb +470 -0
  258. data/lib/state_machine/transition_collection.rb +245 -0
  259. data/lib/state_machine/version.rb +3 -0
  260. data/lib/state_machine/yard.rb +8 -0
  261. data/lib/state_machine/yard/handlers.rb +12 -0
  262. data/lib/state_machine/yard/handlers/base.rb +32 -0
  263. data/lib/state_machine/yard/handlers/event.rb +25 -0
  264. data/lib/state_machine/yard/handlers/machine.rb +344 -0
  265. data/lib/state_machine/yard/handlers/state.rb +25 -0
  266. data/lib/state_machine/yard/handlers/transition.rb +47 -0
  267. data/lib/state_machine/yard/templates.rb +3 -0
  268. data/lib/state_machine/yard/templates/default/class/html/setup.rb +30 -0
  269. data/lib/state_machine/yard/templates/default/class/html/state_machines.erb +12 -0
  270. data/lib/tasks/state_machine.rake +1 -0
  271. data/lib/tasks/state_machine.rb +30 -0
  272. data/lib/yard-state_machine.rb +2 -0
  273. data/test/files/en.yml +17 -0
  274. data/test/files/switch.rb +15 -0
  275. data/test/functional/state_machine_test.rb +1066 -0
  276. data/test/test_helper.rb +7 -0
  277. data/test/unit/assertions_test.rb +40 -0
  278. data/test/unit/branch_test.rb +969 -0
  279. data/test/unit/callback_test.rb +704 -0
  280. data/test/unit/error_test.rb +43 -0
  281. data/test/unit/eval_helpers_test.rb +270 -0
  282. data/test/unit/event_collection_test.rb +398 -0
  283. data/test/unit/event_test.rb +1196 -0
  284. data/test/unit/graph_test.rb +98 -0
  285. data/test/unit/helper_module_test.rb +17 -0
  286. data/test/unit/integrations/active_model_test.rb +1245 -0
  287. data/test/unit/integrations/active_record_test.rb +2551 -0
  288. data/test/unit/integrations/base_test.rb +104 -0
  289. data/test/unit/integrations/data_mapper_test.rb +2194 -0
  290. data/test/unit/integrations/mongo_mapper_test.rb +2026 -0
  291. data/test/unit/integrations/mongoid_test.rb +2309 -0
  292. data/test/unit/integrations/sequel_test.rb +1896 -0
  293. data/test/unit/integrations_test.rb +83 -0
  294. data/test/unit/invalid_event_test.rb +20 -0
  295. data/test/unit/invalid_parallel_transition_test.rb +18 -0
  296. data/test/unit/invalid_transition_test.rb +115 -0
  297. data/test/unit/machine_collection_test.rb +603 -0
  298. data/test/unit/machine_test.rb +3407 -0
  299. data/test/unit/matcher_helpers_test.rb +37 -0
  300. data/test/unit/matcher_test.rb +155 -0
  301. data/test/unit/node_collection_test.rb +362 -0
  302. data/test/unit/path_collection_test.rb +266 -0
  303. data/test/unit/path_test.rb +485 -0
  304. data/test/unit/state_collection_test.rb +352 -0
  305. data/test/unit/state_context_test.rb +441 -0
  306. data/test/unit/state_machine_test.rb +31 -0
  307. data/test/unit/state_test.rb +1101 -0
  308. data/test/unit/transition_collection_test.rb +2168 -0
  309. data/test/unit/transition_test.rb +1558 -0
  310. metadata +438 -0
@@ -0,0 +1,3407 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class MachineByDefaultTest < Test::Unit::TestCase
4
+ def setup
5
+ @klass = Class.new
6
+ @machine = StateMachine::Machine.new(@klass)
7
+ @object = @klass.new
8
+ end
9
+
10
+ def test_should_have_an_owner_class
11
+ assert_equal @klass, @machine.owner_class
12
+ end
13
+
14
+ def test_should_have_a_name
15
+ assert_equal :state, @machine.name
16
+ end
17
+
18
+ def test_should_have_an_attribute
19
+ assert_equal :state, @machine.attribute
20
+ end
21
+
22
+ def test_should_prefix_custom_attributes_with_attribute
23
+ assert_equal :state_event, @machine.attribute(:event)
24
+ end
25
+
26
+ def test_should_have_an_initial_state
27
+ assert_not_nil @machine.initial_state(@object)
28
+ end
29
+
30
+ def test_should_have_a_nil_initial_state
31
+ assert_nil @machine.initial_state(@object).value
32
+ end
33
+
34
+ def test_should_not_have_any_events
35
+ assert !@machine.events.any?
36
+ end
37
+
38
+ def test_should_not_have_any_before_callbacks
39
+ assert @machine.callbacks[:before].empty?
40
+ end
41
+
42
+ def test_should_not_have_any_after_callbacks
43
+ assert @machine.callbacks[:after].empty?
44
+ end
45
+
46
+ def test_should_not_have_any_failure_callbacks
47
+ assert @machine.callbacks[:failure].empty?
48
+ end
49
+
50
+ def test_should_not_have_an_action
51
+ assert_nil @machine.action
52
+ end
53
+
54
+ def test_should_use_tranactions
55
+ assert_equal true, @machine.use_transactions
56
+ end
57
+
58
+ def test_should_not_have_a_namespace
59
+ assert_nil @machine.namespace
60
+ end
61
+
62
+ def test_should_have_a_nil_state
63
+ assert_equal [nil], @machine.states.keys
64
+ end
65
+
66
+ def test_should_set_initial_on_nil_state
67
+ assert @machine.state(nil).initial
68
+ end
69
+
70
+ def test_should_generate_default_messages
71
+ assert_equal 'is invalid', @machine.generate_message(:invalid)
72
+ assert_equal 'cannot transition when parked', @machine.generate_message(:invalid_event, [[:state, :parked]])
73
+ assert_equal 'cannot transition via "park"', @machine.generate_message(:invalid_transition, [[:event, :park]])
74
+ end
75
+
76
+ def test_should_not_be_extended_by_the_base_integration
77
+ assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::Base)
78
+ end
79
+
80
+ def test_should_not_be_extended_by_the_active_model_integration
81
+ assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::ActiveModel)
82
+ end
83
+
84
+ def test_should_not_be_extended_by_the_active_record_integration
85
+ assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::ActiveRecord)
86
+ end
87
+
88
+ def test_should_not_be_extended_by_the_datamapper_integration
89
+ assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::DataMapper)
90
+ end
91
+
92
+ def test_should_not_be_extended_by_the_mongo_mapper_integration
93
+ assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::MongoMapper)
94
+ end
95
+
96
+ def test_should_not_be_extended_by_the_sequel_integration
97
+ assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::Sequel)
98
+ end
99
+
100
+ def test_should_define_a_reader_attribute_for_the_attribute
101
+ assert @object.respond_to?(:state)
102
+ end
103
+
104
+ def test_should_define_a_writer_attribute_for_the_attribute
105
+ assert @object.respond_to?(:state=)
106
+ end
107
+
108
+ def test_should_define_a_predicate_for_the_attribute
109
+ assert @object.respond_to?(:state?)
110
+ end
111
+
112
+ def test_should_define_a_name_reader_for_the_attribute
113
+ assert @object.respond_to?(:state_name)
114
+ end
115
+
116
+ def test_should_define_an_event_reader_for_the_attribute
117
+ assert @object.respond_to?(:state_events)
118
+ end
119
+
120
+ def test_should_define_a_transition_reader_for_the_attribute
121
+ assert @object.respond_to?(:state_transitions)
122
+ end
123
+
124
+ def test_should_define_a_path_reader_for_the_attribute
125
+ assert @object.respond_to?(:state_paths)
126
+ end
127
+
128
+ def test_should_define_an_event_runner_for_the_attribute
129
+ assert @object.respond_to?(:fire_state_event)
130
+ end
131
+
132
+ def test_should_not_define_an_event_attribute_reader
133
+ assert !@object.respond_to?(:state_event)
134
+ end
135
+
136
+ def test_should_not_define_an_event_attribute_writer
137
+ assert !@object.respond_to?(:state_event=)
138
+ end
139
+
140
+ def test_should_not_define_an_event_transition_attribute_reader
141
+ assert !@object.respond_to?(:state_event_transition)
142
+ end
143
+
144
+ def test_should_not_define_an_event_transition_attribute_writer
145
+ assert !@object.respond_to?(:state_event_transition=)
146
+ end
147
+
148
+ def test_should_define_a_human_attribute_name_reader_for_the_attribute
149
+ assert @klass.respond_to?(:human_state_name)
150
+ end
151
+
152
+ def test_should_define_a_human_event_name_reader_for_the_attribute
153
+ assert @klass.respond_to?(:human_state_event_name)
154
+ end
155
+
156
+ def test_should_not_define_singular_with_scope
157
+ assert !@klass.respond_to?(:with_state)
158
+ end
159
+
160
+ def test_should_not_define_singular_without_scope
161
+ assert !@klass.respond_to?(:without_state)
162
+ end
163
+
164
+ def test_should_not_define_plural_with_scope
165
+ assert !@klass.respond_to?(:with_states)
166
+ end
167
+
168
+ def test_should_not_define_plural_without_scope
169
+ assert !@klass.respond_to?(:without_states)
170
+ end
171
+
172
+ def test_should_extend_owner_class_with_class_methods
173
+ assert((class << @klass; ancestors; end).include?(StateMachine::ClassMethods))
174
+ end
175
+
176
+ def test_should_include_instance_methods_in_owner_class
177
+ assert @klass.included_modules.include?(StateMachine::InstanceMethods)
178
+ end
179
+
180
+ def test_should_define_state_machines_reader
181
+ expected = {:state => @machine}
182
+ assert_equal expected, @klass.state_machines
183
+ end
184
+ end
185
+
186
+ class MachineWithCustomNameTest < Test::Unit::TestCase
187
+ def setup
188
+ @klass = Class.new
189
+ @machine = StateMachine::Machine.new(@klass, :status)
190
+ @object = @klass.new
191
+ end
192
+
193
+ def test_should_use_custom_name
194
+ assert_equal :status, @machine.name
195
+ end
196
+
197
+ def test_should_use_custom_name_for_attribute
198
+ assert_equal :status, @machine.attribute
199
+ end
200
+
201
+ def test_should_prefix_custom_attributes_with_custom_name
202
+ assert_equal :status_event, @machine.attribute(:event)
203
+ end
204
+
205
+ def test_should_define_a_reader_attribute_for_the_attribute
206
+ assert @object.respond_to?(:status)
207
+ end
208
+
209
+ def test_should_define_a_writer_attribute_for_the_attribute
210
+ assert @object.respond_to?(:status=)
211
+ end
212
+
213
+ def test_should_define_a_predicate_for_the_attribute
214
+ assert @object.respond_to?(:status?)
215
+ end
216
+
217
+ def test_should_define_a_name_reader_for_the_attribute
218
+ assert @object.respond_to?(:status_name)
219
+ end
220
+
221
+ def test_should_define_an_event_reader_for_the_attribute
222
+ assert @object.respond_to?(:status_events)
223
+ end
224
+
225
+ def test_should_define_a_transition_reader_for_the_attribute
226
+ assert @object.respond_to?(:status_transitions)
227
+ end
228
+
229
+ def test_should_define_an_event_runner_for_the_attribute
230
+ assert @object.respond_to?(:fire_status_event)
231
+ end
232
+
233
+ def test_should_define_a_human_attribute_name_reader_for_the_attribute
234
+ assert @klass.respond_to?(:human_status_name)
235
+ end
236
+
237
+ def test_should_define_a_human_event_name_reader_for_the_attribute
238
+ assert @klass.respond_to?(:human_status_event_name)
239
+ end
240
+ end
241
+
242
+ class MachineWithoutInitializationTest < Test::Unit::TestCase
243
+ def setup
244
+ @klass = Class.new do
245
+ def initialize(attributes = {})
246
+ attributes.each {|attr, value| send("#{attr}=", value)}
247
+ super()
248
+ end
249
+ end
250
+
251
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked, :initialize => false)
252
+ end
253
+
254
+ def test_should_not_have_an_initial_state
255
+ object = @klass.new
256
+ assert_nil object.state
257
+ end
258
+
259
+ def test_should_still_allow_manual_initialization
260
+ @klass.send(:include, Module.new do
261
+ def initialize(attributes = {})
262
+ super()
263
+ initialize_state_machines
264
+ end
265
+ end)
266
+
267
+ object = @klass.new
268
+ assert_equal 'parked', object.state
269
+ end
270
+ end
271
+
272
+ class MachineWithStaticInitialStateTest < Test::Unit::TestCase
273
+ def setup
274
+ @klass = Class.new
275
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
276
+ end
277
+
278
+ def test_should_not_have_dynamic_initial_state
279
+ assert !@machine.dynamic_initial_state?
280
+ end
281
+
282
+ def test_should_have_an_initial_state
283
+ object = @klass.new
284
+ assert_equal 'parked', @machine.initial_state(object).value
285
+ end
286
+
287
+ def test_should_write_to_attribute_when_initializing_state
288
+ object = @klass.allocate
289
+ @machine.initialize_state(object)
290
+ assert_equal 'parked', object.state
291
+ end
292
+
293
+ def test_should_set_initial_on_state_object
294
+ assert @machine.state(:parked).initial
295
+ end
296
+
297
+ def test_should_set_initial_state_on_created_object
298
+ assert_equal 'parked', @klass.new.state
299
+ end
300
+
301
+ def test_not_set_initial_state_even_if_not_empty
302
+ @klass.class_eval do
303
+ def initialize(attributes = {})
304
+ self.state = 'idling'
305
+ super()
306
+ end
307
+ end
308
+ object = @klass.new
309
+ assert_equal 'idling', object.state
310
+ end
311
+
312
+ def test_should_set_initial_state_prior_to_initialization
313
+ base = Class.new do
314
+ attr_accessor :state_on_init
315
+
316
+ def initialize
317
+ self.state_on_init = state
318
+ end
319
+ end
320
+ klass = Class.new(base)
321
+ StateMachine::Machine.new(klass, :initial => :parked)
322
+
323
+ assert_equal 'parked', klass.new.state_on_init
324
+ end
325
+
326
+ def test_should_be_included_in_known_states
327
+ assert_equal [:parked], @machine.states.keys
328
+ end
329
+ end
330
+
331
+ class MachineWithDynamicInitialStateTest < Test::Unit::TestCase
332
+ def setup
333
+ @klass = Class.new do
334
+ attr_accessor :initial_state
335
+ end
336
+ @machine = StateMachine::Machine.new(@klass, :initial => lambda {|object| object.initial_state || :default})
337
+ @machine.state :parked, :idling, :default
338
+ @object = @klass.new
339
+ end
340
+
341
+ def test_should_have_dynamic_initial_state
342
+ assert @machine.dynamic_initial_state?
343
+ end
344
+
345
+ def test_should_use_the_record_for_determining_the_initial_state
346
+ @object.initial_state = :parked
347
+ assert_equal :parked, @machine.initial_state(@object).name
348
+
349
+ @object.initial_state = :idling
350
+ assert_equal :idling, @machine.initial_state(@object).name
351
+ end
352
+
353
+ def test_should_write_to_attribute_when_initializing_state
354
+ object = @klass.allocate
355
+ object.initial_state = :parked
356
+ @machine.initialize_state(object)
357
+ assert_equal 'parked', object.state
358
+ end
359
+
360
+ def test_should_set_initial_state_on_created_object
361
+ assert_equal 'default', @object.state
362
+ end
363
+
364
+ def test_should_not_set_initial_state_even_if_not_empty
365
+ @klass.class_eval do
366
+ def initialize(attributes = {})
367
+ self.state = 'parked'
368
+ super()
369
+ end
370
+ end
371
+ object = @klass.new
372
+ assert_equal 'parked', object.state
373
+ end
374
+
375
+ def test_should_set_initial_state_after_initialization
376
+ base = Class.new do
377
+ attr_accessor :state_on_init
378
+
379
+ def initialize
380
+ self.state_on_init = state
381
+ end
382
+ end
383
+ klass = Class.new(base)
384
+ machine = StateMachine::Machine.new(klass, :initial => lambda {|object| :parked})
385
+ machine.state :parked
386
+
387
+ assert_nil klass.new.state_on_init
388
+ end
389
+
390
+ def test_should_not_be_included_in_known_states
391
+ assert_equal [:parked, :idling, :default], @machine.states.map {|state| state.name}
392
+ end
393
+ end
394
+
395
+ class MachineStateInitializationTest < Test::Unit::TestCase
396
+ def setup
397
+ @klass = Class.new
398
+ @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :initialize => false)
399
+
400
+ @object = @klass.new
401
+ @object.state = nil
402
+ end
403
+
404
+ def test_should_set_states_if_nil
405
+ @machine.initialize_state(@object)
406
+
407
+ assert_equal 'parked', @object.state
408
+ end
409
+
410
+ def test_should_set_states_if_empty
411
+ @object.state = ''
412
+ @machine.initialize_state(@object)
413
+
414
+ assert_equal 'parked', @object.state
415
+ end
416
+
417
+ def test_should_not_set_states_if_not_empty
418
+ @object.state = 'idling'
419
+ @machine.initialize_state(@object)
420
+
421
+ assert_equal 'idling', @object.state
422
+ end
423
+
424
+ def test_should_set_states_if_not_empty_and_forced
425
+ @object.state = 'idling'
426
+ @machine.initialize_state(@object, :force => true)
427
+
428
+ assert_equal 'parked', @object.state
429
+ end
430
+
431
+ def test_should_not_set_state_if_nil_and_nil_is_valid_state
432
+ @machine.state :initial, :value => nil
433
+ @machine.initialize_state(@object)
434
+
435
+ assert_nil @object.state
436
+ end
437
+
438
+ def test_should_write_to_hash_if_specified
439
+ @machine.initialize_state(@object, :to => hash = {})
440
+ assert_equal({'state' => 'parked'}, hash)
441
+ end
442
+
443
+ def test_should_not_write_to_object_if_writing_to_hash
444
+ @machine.initialize_state(@object, :to => {})
445
+ assert_nil @object.state
446
+ end
447
+ end
448
+
449
+ class MachineWithCustomActionTest < Test::Unit::TestCase
450
+ def setup
451
+ @machine = StateMachine::Machine.new(Class.new, :action => :save)
452
+ end
453
+
454
+ def test_should_use_the_custom_action
455
+ assert_equal :save, @machine.action
456
+ end
457
+ end
458
+
459
+ class MachineWithNilActionTest < Test::Unit::TestCase
460
+ def setup
461
+ integration = Module.new do
462
+ include StateMachine::Integrations::Base
463
+
464
+ @defaults = {:action => :save}
465
+ end
466
+ StateMachine::Integrations.const_set('Custom', integration)
467
+ @machine = StateMachine::Machine.new(Class.new, :action => nil, :integration => :custom)
468
+ end
469
+
470
+ def test_should_have_a_nil_action
471
+ assert_nil @machine.action
472
+ end
473
+
474
+ def teardown
475
+ StateMachine::Integrations.send(:remove_const, 'Custom')
476
+ end
477
+ end
478
+
479
+ class MachineWithoutIntegrationTest < Test::Unit::TestCase
480
+ def setup
481
+ @klass = Class.new
482
+ @machine = StateMachine::Machine.new(@klass)
483
+ @object = @klass.new
484
+ end
485
+
486
+ def test_transaction_should_yield
487
+ @yielded = false
488
+ @machine.within_transaction(@object) do
489
+ @yielded = true
490
+ end
491
+
492
+ assert @yielded
493
+ end
494
+
495
+ def test_invalidation_should_do_nothing
496
+ assert_nil @machine.invalidate(@object, :state, :invalid_transition, [[:event, 'park']])
497
+ end
498
+
499
+ def test_reset_should_do_nothing
500
+ assert_nil @machine.reset(@object)
501
+ end
502
+
503
+ def test_errors_for_should_be_empty
504
+ assert_equal '', @machine.errors_for(@object)
505
+ end
506
+ end
507
+
508
+ class MachineWithCustomIntegrationTest < Test::Unit::TestCase
509
+ def setup
510
+ integration = Module.new do
511
+ include StateMachine::Integrations::Base
512
+
513
+ def self.matching_ancestors
514
+ ['MachineWithCustomIntegrationTest::Vehicle']
515
+ end
516
+ end
517
+
518
+ StateMachine::Integrations.const_set('Custom', integration)
519
+
520
+ superclass = Class.new
521
+ self.class.const_set('Vehicle', superclass)
522
+
523
+ @klass = Class.new(superclass)
524
+ end
525
+
526
+ def test_should_be_extended_by_the_integration_if_explicit
527
+ machine = StateMachine::Machine.new(@klass, :integration => :custom)
528
+ assert((class << machine; ancestors; end).include?(StateMachine::Integrations::Custom))
529
+ end
530
+
531
+ def test_should_not_be_extended_by_the_integration_if_implicit_but_not_available
532
+ StateMachine::Integrations::Custom.class_eval do
533
+ class << self; remove_method :matching_ancestors; end
534
+ def self.matching_ancestors
535
+ []
536
+ end
537
+ end
538
+
539
+ machine = StateMachine::Machine.new(@klass)
540
+ assert(!(class << machine; ancestors; end).include?(StateMachine::Integrations::Custom))
541
+ end
542
+
543
+ def test_should_not_be_extended_by_the_integration_if_implicit_but_not_matched
544
+ StateMachine::Integrations::Custom.class_eval do
545
+ class << self; remove_method :matching_ancestors; end
546
+ def self.matching_ancestors
547
+ []
548
+ end
549
+ end
550
+
551
+ machine = StateMachine::Machine.new(@klass)
552
+ assert(!(class << machine; ancestors; end).include?(StateMachine::Integrations::Custom))
553
+ end
554
+
555
+ def test_should_be_extended_by_the_integration_if_implicit_and_available_and_matches
556
+ machine = StateMachine::Machine.new(@klass)
557
+ assert((class << machine; ancestors; end).include?(StateMachine::Integrations::Custom))
558
+ end
559
+
560
+ def test_should_not_be_extended_by_the_integration_if_nil
561
+ machine = StateMachine::Machine.new(@klass, :integration => nil)
562
+ assert(!(class << machine; ancestors; end).include?(StateMachine::Integrations::Custom))
563
+ end
564
+
565
+ def test_should_not_be_extended_by_the_integration_if_false
566
+ machine = StateMachine::Machine.new(@klass, :integration => false)
567
+ assert(!(class << machine; ancestors; end).include?(StateMachine::Integrations::Custom))
568
+ end
569
+
570
+ def teardown
571
+ self.class.send(:remove_const, 'Vehicle')
572
+ StateMachine::Integrations.send(:remove_const, 'Custom')
573
+ end
574
+ end
575
+
576
+ class MachineWithIntegrationTest < Test::Unit::TestCase
577
+ def setup
578
+ StateMachine::Integrations.const_set('Custom', Module.new do
579
+ include StateMachine::Integrations::Base
580
+
581
+ @defaults = {:action => :save, :use_transactions => false}
582
+
583
+ attr_reader :initialized, :with_scopes, :without_scopes, :ran_transaction
584
+
585
+ def after_initialize
586
+ @initialized = true
587
+ end
588
+
589
+ def create_with_scope(name)
590
+ (@with_scopes ||= []) << name
591
+ lambda {}
592
+ end
593
+
594
+ def create_without_scope(name)
595
+ (@without_scopes ||= []) << name
596
+ lambda {}
597
+ end
598
+
599
+ def transaction(object)
600
+ @ran_transaction = true
601
+ yield
602
+ end
603
+ end)
604
+
605
+ @machine = StateMachine::Machine.new(Class.new, :integration => :custom)
606
+ end
607
+
608
+ def test_should_call_after_initialize_hook
609
+ assert @machine.initialized
610
+ end
611
+
612
+ def test_should_use_the_default_action
613
+ assert_equal :save, @machine.action
614
+ end
615
+
616
+ def test_should_use_the_custom_action_if_specified
617
+ machine = StateMachine::Machine.new(Class.new, :integration => :custom, :action => :save!)
618
+ assert_equal :save!, machine.action
619
+ end
620
+
621
+ def test_should_use_the_default_use_transactions
622
+ assert_equal false, @machine.use_transactions
623
+ end
624
+
625
+ def test_should_use_the_custom_use_transactions_if_specified
626
+ machine = StateMachine::Machine.new(Class.new, :integration => :custom, :use_transactions => true)
627
+ assert_equal true, machine.use_transactions
628
+ end
629
+
630
+ def test_should_define_a_singular_and_plural_with_scope
631
+ assert_equal %w(with_state with_states), @machine.with_scopes
632
+ end
633
+
634
+ def test_should_define_a_singular_and_plural_without_scope
635
+ assert_equal %w(without_state without_states), @machine.without_scopes
636
+ end
637
+
638
+ def teardown
639
+ StateMachine::Integrations.send(:remove_const, 'Custom')
640
+ end
641
+ end
642
+
643
+ class MachineWithActionUndefinedTest < Test::Unit::TestCase
644
+ def setup
645
+ @klass = Class.new
646
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
647
+ @object = @klass.new
648
+ end
649
+
650
+ def test_should_define_an_event_attribute_reader
651
+ assert @object.respond_to?(:state_event)
652
+ end
653
+
654
+ def test_should_define_an_event_attribute_writer
655
+ assert @object.respond_to?(:state_event=)
656
+ end
657
+
658
+ def test_should_define_an_event_transition_attribute_reader
659
+ assert @object.respond_to?(:state_event_transition, true)
660
+ end
661
+
662
+ def test_should_define_an_event_transition_attribute_writer
663
+ assert @object.respond_to?(:state_event_transition=, true)
664
+ end
665
+
666
+ def test_should_not_define_action
667
+ assert !@object.respond_to?(:save)
668
+ end
669
+
670
+ def test_should_not_mark_action_hook_as_defined
671
+ assert !@machine.action_hook?
672
+ end
673
+ end
674
+
675
+ class MachineWithActionDefinedInClassTest < Test::Unit::TestCase
676
+ def setup
677
+ @klass = Class.new do
678
+ def save
679
+ end
680
+ end
681
+
682
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
683
+ @object = @klass.new
684
+ end
685
+
686
+ def test_should_define_an_event_attribute_reader
687
+ assert @object.respond_to?(:state_event)
688
+ end
689
+
690
+ def test_should_define_an_event_attribute_writer
691
+ assert @object.respond_to?(:state_event=)
692
+ end
693
+
694
+ def test_should_define_an_event_transition_attribute_reader
695
+ assert @object.respond_to?(:state_event_transition, true)
696
+ end
697
+
698
+ def test_should_define_an_event_transition_attribute_writer
699
+ assert @object.respond_to?(:state_event_transition=, true)
700
+ end
701
+
702
+ def test_should_not_define_action
703
+ assert !@klass.ancestors.any? {|ancestor| ancestor != @klass && ancestor.method_defined?(:save)}
704
+ end
705
+
706
+ def test_should_not_mark_action_hook_as_defined
707
+ assert !@machine.action_hook?
708
+ end
709
+ end
710
+
711
+ class MachineWithActionDefinedInIncludedModuleTest < Test::Unit::TestCase
712
+ def setup
713
+ @mod = mod = Module.new do
714
+ def save
715
+ end
716
+ end
717
+
718
+ @klass = Class.new do
719
+ include mod
720
+ end
721
+
722
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
723
+ @object = @klass.new
724
+ end
725
+
726
+ def test_should_define_an_event_attribute_reader
727
+ assert @object.respond_to?(:state_event)
728
+ end
729
+
730
+ def test_should_define_an_event_attribute_writer
731
+ assert @object.respond_to?(:state_event=)
732
+ end
733
+
734
+ def test_should_define_an_event_transition_attribute_reader
735
+ assert @object.respond_to?(:state_event_transition, true)
736
+ end
737
+
738
+ def test_should_define_an_event_transition_attribute_writer
739
+ assert @object.respond_to?(:state_event_transition=, true)
740
+ end
741
+
742
+ def test_should_define_action
743
+ assert @klass.ancestors.any? {|ancestor| ![@klass, @mod].include?(ancestor) && ancestor.method_defined?(:save)}
744
+ end
745
+
746
+ def test_should_keep_action_public
747
+ assert @klass.public_method_defined?(:save)
748
+ end
749
+
750
+ def test_should_mark_action_hook_as_defined
751
+ assert @machine.action_hook?
752
+ end
753
+ end
754
+
755
+ class MachineWithActionDefinedInSuperclassTest < Test::Unit::TestCase
756
+ def setup
757
+ @superclass = Class.new do
758
+ def save
759
+ end
760
+ end
761
+ @klass = Class.new(@superclass)
762
+
763
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
764
+ @object = @klass.new
765
+ end
766
+
767
+ def test_should_define_an_event_attribute_reader
768
+ assert @object.respond_to?(:state_event)
769
+ end
770
+
771
+ def test_should_define_an_event_attribute_writer
772
+ assert @object.respond_to?(:state_event=)
773
+ end
774
+
775
+ def test_should_define_an_event_transition_attribute_reader
776
+ assert @object.respond_to?(:state_event_transition, true)
777
+ end
778
+
779
+ def test_should_define_an_event_transition_attribute_writer
780
+ assert @object.respond_to?(:state_event_transition=, true)
781
+ end
782
+
783
+ def test_should_define_action
784
+ assert @klass.ancestors.any? {|ancestor| ![@klass, @superclass].include?(ancestor) && ancestor.method_defined?(:save)}
785
+ end
786
+
787
+ def test_should_keep_action_public
788
+ assert @klass.public_method_defined?(:save)
789
+ end
790
+
791
+ def test_should_mark_action_hook_as_defined
792
+ assert @machine.action_hook?
793
+ end
794
+ end
795
+
796
+ class MachineWithPrivateActionTest < Test::Unit::TestCase
797
+ def setup
798
+ @superclass = Class.new do
799
+ private
800
+ def save
801
+ end
802
+ end
803
+ @klass = Class.new(@superclass)
804
+
805
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
806
+ @object = @klass.new
807
+ end
808
+
809
+ def test_should_define_an_event_attribute_reader
810
+ assert @object.respond_to?(:state_event)
811
+ end
812
+
813
+ def test_should_define_an_event_attribute_writer
814
+ assert @object.respond_to?(:state_event=)
815
+ end
816
+
817
+ def test_should_define_an_event_transition_attribute_reader
818
+ assert @object.respond_to?(:state_event_transition, true)
819
+ end
820
+
821
+ def test_should_define_an_event_transition_attribute_writer
822
+ assert @object.respond_to?(:state_event_transition=, true)
823
+ end
824
+
825
+ def test_should_define_action
826
+ assert @klass.ancestors.any? {|ancestor| ![@klass, @superclass].include?(ancestor) && ancestor.private_method_defined?(:save)}
827
+ end
828
+
829
+ def test_should_keep_action_private
830
+ assert @klass.private_method_defined?(:save)
831
+ end
832
+
833
+ def test_should_mark_action_hook_as_defined
834
+ assert @machine.action_hook?
835
+ end
836
+ end
837
+
838
+ class MachineWithActionAlreadyOverriddenTest < Test::Unit::TestCase
839
+ def setup
840
+ @superclass = Class.new do
841
+ def save
842
+ end
843
+ end
844
+ @klass = Class.new(@superclass)
845
+
846
+ StateMachine::Machine.new(@klass, :action => :save)
847
+ @machine = StateMachine::Machine.new(@klass, :status, :action => :save)
848
+ @object = @klass.new
849
+ end
850
+
851
+ def test_should_not_redefine_action
852
+ assert_equal 1, @klass.ancestors.select {|ancestor| ![@klass, @superclass].include?(ancestor) && ancestor.method_defined?(:save)}.length
853
+ end
854
+
855
+ def test_should_mark_action_hook_as_defined
856
+ assert @machine.action_hook?
857
+ end
858
+ end
859
+
860
+ class MachineWithCustomPluralTest < Test::Unit::TestCase
861
+ def setup
862
+ @integration = Module.new do
863
+ include StateMachine::Integrations::Base
864
+
865
+ class << self; attr_accessor :with_scopes, :without_scopes; end
866
+ @with_scopes = []
867
+ @without_scopes = []
868
+
869
+ def create_with_scope(name)
870
+ StateMachine::Integrations::Custom.with_scopes << name
871
+ lambda {}
872
+ end
873
+
874
+ def create_without_scope(name)
875
+ StateMachine::Integrations::Custom.without_scopes << name
876
+ lambda {}
877
+ end
878
+ end
879
+
880
+ StateMachine::Integrations.const_set('Custom', @integration)
881
+ end
882
+
883
+ def test_should_define_a_singular_and_plural_with_scope
884
+ StateMachine::Machine.new(Class.new, :integration => :custom, :plural => 'staties')
885
+ assert_equal %w(with_state with_staties), @integration.with_scopes
886
+ end
887
+
888
+ def test_should_define_a_singular_and_plural_without_scope
889
+ StateMachine::Machine.new(Class.new, :integration => :custom, :plural => 'staties')
890
+ assert_equal %w(without_state without_staties), @integration.without_scopes
891
+ end
892
+
893
+ def test_should_define_single_with_scope_if_singular_same_as_plural
894
+ StateMachine::Machine.new(Class.new, :integration => :custom, :plural => 'state')
895
+ assert_equal %w(with_state), @integration.with_scopes
896
+ end
897
+
898
+ def test_should_define_single_without_scope_if_singular_same_as_plural
899
+ StateMachine::Machine.new(Class.new, :integration => :custom, :plural => 'state')
900
+ assert_equal %w(without_state), @integration.without_scopes
901
+ end
902
+
903
+ def teardown
904
+ StateMachine::Integrations.send(:remove_const, 'Custom')
905
+ end
906
+ end
907
+
908
+ class MachineWithCustomInvalidationTest < Test::Unit::TestCase
909
+ def setup
910
+ @integration = Module.new do
911
+ include StateMachine::Integrations::Base
912
+
913
+ def invalidate(object, attribute, message, values = [])
914
+ object.error = generate_message(message, values)
915
+ end
916
+ end
917
+ StateMachine::Integrations.const_set('Custom', @integration)
918
+
919
+ @klass = Class.new do
920
+ attr_accessor :error
921
+ end
922
+
923
+ @machine = StateMachine::Machine.new(@klass, :integration => :custom, :messages => {:invalid_transition => 'cannot %s'})
924
+ @machine.state :parked
925
+
926
+ @object = @klass.new
927
+ @object.state = 'parked'
928
+ end
929
+
930
+ def test_generate_custom_message
931
+ assert_equal 'cannot park', @machine.generate_message(:invalid_transition, [[:event, :park]])
932
+ end
933
+
934
+ def test_use_custom_message
935
+ @machine.invalidate(@object, :state, :invalid_transition, [[:event, 'park']])
936
+ assert_equal 'cannot park', @object.error
937
+ end
938
+
939
+ def teardown
940
+ StateMachine::Integrations.send(:remove_const, 'Custom')
941
+ end
942
+ end
943
+
944
+ class MachineTest < Test::Unit::TestCase
945
+ def test_should_raise_exception_if_invalid_option_specified
946
+ assert_raise(ArgumentError) {StateMachine::Machine.new(Class.new, :invalid => true)}
947
+ end
948
+
949
+ def test_should_not_raise_exception_if_custom_messages_specified
950
+ assert_nothing_raised {StateMachine::Machine.new(Class.new, :messages => {:invalid_transition => 'custom'})}
951
+ end
952
+
953
+ def test_should_evaluate_a_block_during_initialization
954
+ called = true
955
+ StateMachine::Machine.new(Class.new) do
956
+ called = respond_to?(:event)
957
+ end
958
+
959
+ assert called
960
+ end
961
+
962
+ def test_should_provide_matcher_helpers_during_initialization
963
+ matchers = []
964
+
965
+ StateMachine::Machine.new(Class.new) do
966
+ matchers = [all, any, same]
967
+ end
968
+
969
+ assert_equal [StateMachine::AllMatcher.instance, StateMachine::AllMatcher.instance, StateMachine::LoopbackMatcher.instance], matchers
970
+ end
971
+ end
972
+
973
+ class MachineAfterBeingCopiedTest < Test::Unit::TestCase
974
+ def setup
975
+ @machine = StateMachine::Machine.new(Class.new, :state, :initial => :parked)
976
+ @machine.event(:ignite) {}
977
+ @machine.before_transition(lambda {})
978
+ @machine.after_transition(lambda {})
979
+ @machine.around_transition(lambda {})
980
+ @machine.after_failure(lambda {})
981
+
982
+ @copied_machine = @machine.clone
983
+ end
984
+
985
+ def test_should_not_have_the_same_collection_of_states
986
+ assert_not_same @copied_machine.states, @machine.states
987
+ end
988
+
989
+ def test_should_copy_each_state
990
+ assert_not_same @copied_machine.states[:parked], @machine.states[:parked]
991
+ end
992
+
993
+ def test_should_update_machine_for_each_state
994
+ assert_equal @copied_machine, @copied_machine.states[:parked].machine
995
+ end
996
+
997
+ def test_should_not_update_machine_for_original_state
998
+ assert_equal @machine, @machine.states[:parked].machine
999
+ end
1000
+
1001
+ def test_should_not_have_the_same_collection_of_events
1002
+ assert_not_same @copied_machine.events, @machine.events
1003
+ end
1004
+
1005
+ def test_should_copy_each_event
1006
+ assert_not_same @copied_machine.events[:ignite], @machine.events[:ignite]
1007
+ end
1008
+
1009
+ def test_should_update_machine_for_each_event
1010
+ assert_equal @copied_machine, @copied_machine.events[:ignite].machine
1011
+ end
1012
+
1013
+ def test_should_not_update_machine_for_original_event
1014
+ assert_equal @machine, @machine.events[:ignite].machine
1015
+ end
1016
+
1017
+ def test_should_not_have_the_same_callbacks
1018
+ assert_not_same @copied_machine.callbacks, @machine.callbacks
1019
+ end
1020
+
1021
+ def test_should_not_have_the_same_before_callbacks
1022
+ assert_not_same @copied_machine.callbacks[:before], @machine.callbacks[:before]
1023
+ end
1024
+
1025
+ def test_should_not_have_the_same_after_callbacks
1026
+ assert_not_same @copied_machine.callbacks[:after], @machine.callbacks[:after]
1027
+ end
1028
+
1029
+ def test_should_not_have_the_same_failure_callbacks
1030
+ assert_not_same @copied_machine.callbacks[:failure], @machine.callbacks[:failure]
1031
+ end
1032
+ end
1033
+
1034
+ class MachineAfterChangingOwnerClassTest < Test::Unit::TestCase
1035
+ def setup
1036
+ @original_class = Class.new
1037
+ @machine = StateMachine::Machine.new(@original_class)
1038
+
1039
+ @new_class = Class.new(@original_class)
1040
+ @new_machine = @machine.clone
1041
+ @new_machine.owner_class = @new_class
1042
+
1043
+ @object = @new_class.new
1044
+ end
1045
+
1046
+ def test_should_update_owner_class
1047
+ assert_equal @new_class, @new_machine.owner_class
1048
+ end
1049
+
1050
+ def test_should_not_change_original_owner_class
1051
+ assert_equal @original_class, @machine.owner_class
1052
+ end
1053
+
1054
+ def test_should_change_the_associated_machine_in_the_new_class
1055
+ assert_equal @new_machine, @new_class.state_machines[:state]
1056
+ end
1057
+
1058
+ def test_should_not_change_the_associated_machine_in_the_original_class
1059
+ assert_equal @machine, @original_class.state_machines[:state]
1060
+ end
1061
+ end
1062
+
1063
+ class MachineAfterChangingInitialState < Test::Unit::TestCase
1064
+ def setup
1065
+ @klass = Class.new
1066
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1067
+ @machine.initial_state = :idling
1068
+
1069
+ @object = @klass.new
1070
+ end
1071
+
1072
+ def test_should_change_the_initial_state
1073
+ assert_equal :idling, @machine.initial_state(@object).name
1074
+ end
1075
+
1076
+ def test_should_include_in_known_states
1077
+ assert_equal [:parked, :idling], @machine.states.map {|state| state.name}
1078
+ end
1079
+
1080
+ def test_should_reset_original_initial_state
1081
+ assert !@machine.state(:parked).initial
1082
+ end
1083
+
1084
+ def test_should_set_new_state_to_initial
1085
+ assert @machine.state(:idling).initial
1086
+ end
1087
+ end
1088
+
1089
+ class MachineWithHelpersTest < Test::Unit::TestCase
1090
+ def setup
1091
+ @klass = Class.new
1092
+ @machine = StateMachine::Machine.new(@klass)
1093
+ @object = @klass.new
1094
+ end
1095
+
1096
+ def test_should_throw_exception_with_invalid_scope
1097
+ assert_raise(RUBY_VERSION < '1.9' ? IndexError : KeyError) { @machine.define_helper(:invalid, :park) {} }
1098
+ end
1099
+ end
1100
+
1101
+ class MachineWithInstanceHelpersTest < Test::Unit::TestCase
1102
+ def setup
1103
+ @klass = Class.new
1104
+ @machine = StateMachine::Machine.new(@klass)
1105
+ @object = @klass.new
1106
+ end
1107
+
1108
+ def test_should_not_redefine_existing_public_methods
1109
+ @klass.class_eval do
1110
+ def park
1111
+ true
1112
+ end
1113
+ end
1114
+
1115
+ @machine.define_helper(:instance, :park) {}
1116
+ assert_equal true, @object.park
1117
+ end
1118
+
1119
+ def test_should_not_redefine_existing_protected_methods
1120
+ @klass.class_eval do
1121
+ protected
1122
+ def park
1123
+ true
1124
+ end
1125
+ end
1126
+
1127
+ @machine.define_helper(:instance, :park) {}
1128
+ assert_equal true, @object.send(:park)
1129
+ end
1130
+
1131
+ def test_should_not_redefine_existing_private_methods
1132
+ @klass.class_eval do
1133
+ private
1134
+ def park
1135
+ true
1136
+ end
1137
+ end
1138
+
1139
+ @machine.define_helper(:instance, :park) {}
1140
+ assert_equal true, @object.send(:park)
1141
+ end
1142
+
1143
+ def test_should_warn_if_defined_in_superclass
1144
+ require 'stringio'
1145
+ @original_stderr, $stderr = $stderr, StringIO.new
1146
+
1147
+ superclass = Class.new do
1148
+ def park
1149
+ end
1150
+ end
1151
+ klass = Class.new(superclass)
1152
+ machine = StateMachine::Machine.new(klass)
1153
+
1154
+ machine.define_helper(:instance, :park) {}
1155
+ assert_equal "Instance method \"park\" is already defined in #{superclass.to_s}, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true.\n", $stderr.string
1156
+ ensure
1157
+ $stderr = @original_stderr
1158
+ end
1159
+
1160
+ def test_should_warn_if_defined_in_multiple_superclasses
1161
+ require 'stringio'
1162
+ @original_stderr, $stderr = $stderr, StringIO.new
1163
+
1164
+ superclass1 = Class.new do
1165
+ def park
1166
+ end
1167
+ end
1168
+ superclass2 = Class.new(superclass1) do
1169
+ def park
1170
+ end
1171
+ end
1172
+ klass = Class.new(superclass2)
1173
+ machine = StateMachine::Machine.new(klass)
1174
+
1175
+ machine.define_helper(:instance, :park) {}
1176
+ assert_equal "Instance method \"park\" is already defined in #{superclass1.to_s}, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true.\n", $stderr.string
1177
+ ensure
1178
+ $stderr = @original_stderr
1179
+ end
1180
+
1181
+ def test_should_warn_if_defined_in_module_prior_to_helper_module
1182
+ require 'stringio'
1183
+ @original_stderr, $stderr = $stderr, StringIO.new
1184
+
1185
+ mod = Module.new do
1186
+ def park
1187
+ end
1188
+ end
1189
+ klass = Class.new do
1190
+ include mod
1191
+ end
1192
+ machine = StateMachine::Machine.new(klass)
1193
+
1194
+ machine.define_helper(:instance, :park) {}
1195
+ assert_equal "Instance method \"park\" is already defined in #{mod.to_s}, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true.\n", $stderr.string
1196
+ ensure
1197
+ $stderr = @original_stderr
1198
+ end
1199
+
1200
+ def test_should_not_warn_if_defined_in_module_after_helper_module
1201
+ require 'stringio'
1202
+ @original_stderr, $stderr = $stderr, StringIO.new
1203
+
1204
+ klass = Class.new
1205
+ machine = StateMachine::Machine.new(klass)
1206
+
1207
+ mod = Module.new do
1208
+ def park
1209
+ end
1210
+ end
1211
+ klass.class_eval do
1212
+ include mod
1213
+ end
1214
+
1215
+ machine.define_helper(:instance, :park) {}
1216
+ assert_equal '', $stderr.string
1217
+ ensure
1218
+ $stderr = @original_stderr
1219
+ end
1220
+
1221
+ def test_should_define_if_ignoring_method_conflicts_and_defined_in_superclass
1222
+ require 'stringio'
1223
+ @original_stderr, $stderr = $stderr, StringIO.new
1224
+ StateMachine::Machine.ignore_method_conflicts = true
1225
+
1226
+ superclass = Class.new do
1227
+ def park
1228
+ end
1229
+ end
1230
+ klass = Class.new(superclass)
1231
+ machine = StateMachine::Machine.new(klass)
1232
+
1233
+ machine.define_helper(:instance, :park) {true}
1234
+ assert_equal '', $stderr.string
1235
+ assert_equal true, klass.new.park
1236
+ ensure
1237
+ StateMachine::Machine.ignore_method_conflicts = false
1238
+ $stderr = @original_stderr
1239
+ end
1240
+
1241
+ def test_should_define_nonexistent_methods
1242
+ @machine.define_helper(:instance, :park) {false}
1243
+ assert_equal false, @object.park
1244
+ end
1245
+
1246
+ def test_should_warn_if_defined_multiple_times
1247
+ require 'stringio'
1248
+ @original_stderr, $stderr = $stderr, StringIO.new
1249
+
1250
+ @machine.define_helper(:instance, :park) {}
1251
+ @machine.define_helper(:instance, :park) {}
1252
+
1253
+ assert_equal "Instance method \"park\" is already defined in #{@klass} :state instance helpers, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true.\n", $stderr.string
1254
+ ensure
1255
+ $stderr = @original_stderr
1256
+ end
1257
+
1258
+ def test_should_pass_context_as_arguments
1259
+ helper_args = nil
1260
+ @machine.define_helper(:instance, :park) {|*args| helper_args = args}
1261
+ @object.park
1262
+ assert_equal 2, helper_args.length
1263
+ assert_equal [@machine, @object], helper_args
1264
+ end
1265
+
1266
+ def test_should_pass_method_arguments_through
1267
+ helper_args = nil
1268
+ @machine.define_helper(:instance, :park) {|*args| helper_args = args}
1269
+ @object.park(1, 2, 3)
1270
+ assert_equal 5, helper_args.length
1271
+ assert_equal [@machine, @object, 1, 2, 3], helper_args
1272
+ end
1273
+
1274
+ def test_should_allow_string_evaluation
1275
+ @machine.define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
1276
+ def park
1277
+ false
1278
+ end
1279
+ end_eval
1280
+ assert_equal false, @object.park
1281
+ end
1282
+ end
1283
+
1284
+ class MachineWithClassHelpersTest < Test::Unit::TestCase
1285
+ def setup
1286
+ @klass = Class.new
1287
+ @machine = StateMachine::Machine.new(@klass)
1288
+ end
1289
+
1290
+ def test_should_not_redefine_existing_public_methods
1291
+ class << @klass
1292
+ def states
1293
+ []
1294
+ end
1295
+ end
1296
+
1297
+ @machine.define_helper(:class, :states) {}
1298
+ assert_equal [], @klass.states
1299
+ end
1300
+
1301
+ def test_should_not_redefine_existing_protected_methods
1302
+ class << @klass
1303
+ protected
1304
+ def states
1305
+ []
1306
+ end
1307
+ end
1308
+
1309
+ @machine.define_helper(:class, :states) {}
1310
+ assert_equal [], @klass.send(:states)
1311
+ end
1312
+
1313
+ def test_should_not_redefine_existing_private_methods
1314
+ class << @klass
1315
+ private
1316
+ def states
1317
+ []
1318
+ end
1319
+ end
1320
+
1321
+ @machine.define_helper(:class, :states) {}
1322
+ assert_equal [], @klass.send(:states)
1323
+ end
1324
+
1325
+ def test_should_warn_if_defined_in_superclass
1326
+ require 'stringio'
1327
+ @original_stderr, $stderr = $stderr, StringIO.new
1328
+
1329
+ superclass = Class.new do
1330
+ def self.park
1331
+ end
1332
+ end
1333
+ klass = Class.new(superclass)
1334
+ machine = StateMachine::Machine.new(klass)
1335
+
1336
+ machine.define_helper(:class, :park) {}
1337
+ assert_equal "Class method \"park\" is already defined in #{superclass.to_s}, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true.\n", $stderr.string
1338
+ ensure
1339
+ $stderr = @original_stderr
1340
+ end
1341
+
1342
+ def test_should_warn_if_defined_in_multiple_superclasses
1343
+ require 'stringio'
1344
+ @original_stderr, $stderr = $stderr, StringIO.new
1345
+
1346
+ superclass1 = Class.new do
1347
+ def self.park
1348
+ end
1349
+ end
1350
+ superclass2 = Class.new(superclass1) do
1351
+ def self.park
1352
+ end
1353
+ end
1354
+ klass = Class.new(superclass2)
1355
+ machine = StateMachine::Machine.new(klass)
1356
+
1357
+ machine.define_helper(:class, :park) {}
1358
+ assert_equal "Class method \"park\" is already defined in #{superclass1.to_s}, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true.\n", $stderr.string
1359
+ ensure
1360
+ $stderr = @original_stderr
1361
+ end
1362
+
1363
+ def test_should_warn_if_defined_in_module_prior_to_helper_module
1364
+ require 'stringio'
1365
+ @original_stderr, $stderr = $stderr, StringIO.new
1366
+
1367
+ mod = Module.new do
1368
+ def park
1369
+ end
1370
+ end
1371
+ klass = Class.new do
1372
+ extend mod
1373
+ end
1374
+ machine = StateMachine::Machine.new(klass)
1375
+
1376
+ machine.define_helper(:class, :park) {}
1377
+ assert_equal "Class method \"park\" is already defined in #{mod.to_s}, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true.\n", $stderr.string
1378
+ ensure
1379
+ $stderr = @original_stderr
1380
+ end
1381
+
1382
+ def test_should_not_warn_if_defined_in_module_after_helper_module
1383
+ require 'stringio'
1384
+ @original_stderr, $stderr = $stderr, StringIO.new
1385
+
1386
+ klass = Class.new
1387
+ machine = StateMachine::Machine.new(klass)
1388
+
1389
+ mod = Module.new do
1390
+ def park
1391
+ end
1392
+ end
1393
+ klass.class_eval do
1394
+ extend mod
1395
+ end
1396
+
1397
+ machine.define_helper(:class, :park) {}
1398
+ assert_equal '', $stderr.string
1399
+ ensure
1400
+ $stderr = @original_stderr
1401
+ end
1402
+
1403
+ def test_should_define_if_ignoring_method_conflicts_and_defined_in_superclass
1404
+ require 'stringio'
1405
+ @original_stderr, $stderr = $stderr, StringIO.new
1406
+ StateMachine::Machine.ignore_method_conflicts = true
1407
+
1408
+ superclass = Class.new do
1409
+ def self.park
1410
+ end
1411
+ end
1412
+ klass = Class.new(superclass)
1413
+ machine = StateMachine::Machine.new(klass)
1414
+
1415
+ machine.define_helper(:class, :park) {true}
1416
+ assert_equal '', $stderr.string
1417
+ assert_equal true, klass.park
1418
+ ensure
1419
+ StateMachine::Machine.ignore_method_conflicts = false
1420
+ $stderr = @original_stderr
1421
+ end
1422
+
1423
+ def test_should_define_nonexistent_methods
1424
+ @machine.define_helper(:class, :states) {[]}
1425
+ assert_equal [], @klass.states
1426
+ end
1427
+
1428
+ def test_should_warn_if_defined_multiple_times
1429
+ require 'stringio'
1430
+ @original_stderr, $stderr = $stderr, StringIO.new
1431
+
1432
+ @machine.define_helper(:class, :states) {}
1433
+ @machine.define_helper(:class, :states) {}
1434
+
1435
+ assert_equal "Class method \"states\" is already defined in #{@klass} :state class helpers, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true.\n", $stderr.string
1436
+ ensure
1437
+ $stderr = @original_stderr
1438
+ end
1439
+
1440
+ def test_should_pass_context_as_arguments
1441
+ helper_args = nil
1442
+ @machine.define_helper(:class, :states) {|*args| helper_args = args}
1443
+ @klass.states
1444
+ assert_equal 2, helper_args.length
1445
+ assert_equal [@machine, @klass], helper_args
1446
+ end
1447
+
1448
+ def test_should_pass_method_arguments_through
1449
+ helper_args = nil
1450
+ @machine.define_helper(:class, :states) {|*args| helper_args = args}
1451
+ @klass.states(1, 2, 3)
1452
+ assert_equal 5, helper_args.length
1453
+ assert_equal [@machine, @klass, 1, 2, 3], helper_args
1454
+ end
1455
+
1456
+ def test_should_allow_string_evaluation
1457
+ @machine.define_helper :class, <<-end_eval, __FILE__, __LINE__ + 1
1458
+ def states
1459
+ []
1460
+ end
1461
+ end_eval
1462
+ assert_equal [], @klass.states
1463
+ end
1464
+ end
1465
+
1466
+ class MachineWithConflictingHelpersBeforeDefinitionTest < Test::Unit::TestCase
1467
+ def setup
1468
+ require 'stringio'
1469
+ @original_stderr, $stderr = $stderr, StringIO.new
1470
+
1471
+ @superclass = Class.new do
1472
+ def self.with_state
1473
+ :with_state
1474
+ end
1475
+
1476
+ def self.with_states
1477
+ :with_states
1478
+ end
1479
+
1480
+ def self.without_state
1481
+ :without_state
1482
+ end
1483
+
1484
+ def self.without_states
1485
+ :without_states
1486
+ end
1487
+
1488
+ def self.human_state_name
1489
+ :human_state_name
1490
+ end
1491
+
1492
+ def self.human_state_event_name
1493
+ :human_state_event_name
1494
+ end
1495
+
1496
+ attr_accessor :status
1497
+
1498
+ def state
1499
+ 'parked'
1500
+ end
1501
+
1502
+ def state=(value)
1503
+ self.status = value
1504
+ end
1505
+
1506
+ def state?
1507
+ true
1508
+ end
1509
+
1510
+ def state_name
1511
+ :parked
1512
+ end
1513
+
1514
+ def human_state_name
1515
+ 'parked'
1516
+ end
1517
+
1518
+ def state_events
1519
+ [:ignite]
1520
+ end
1521
+
1522
+ def state_transitions
1523
+ [{:parked => :idling}]
1524
+ end
1525
+
1526
+ def state_paths
1527
+ [[{:parked => :idling}]]
1528
+ end
1529
+
1530
+ def fire_state_event
1531
+ true
1532
+ end
1533
+ end
1534
+ @klass = Class.new(@superclass)
1535
+
1536
+ StateMachine::Integrations.const_set('Custom', Module.new do
1537
+ include StateMachine::Integrations::Base
1538
+
1539
+ def create_with_scope(name)
1540
+ lambda {|klass, values| []}
1541
+ end
1542
+
1543
+ def create_without_scope(name)
1544
+ lambda {|klass, values| []}
1545
+ end
1546
+ end)
1547
+
1548
+ @machine = StateMachine::Machine.new(@klass, :integration => :custom)
1549
+ @machine.state :parked, :idling
1550
+ @machine.event :ignite
1551
+ @object = @klass.new
1552
+ end
1553
+
1554
+ def test_should_not_redefine_singular_with_scope
1555
+ assert_equal :with_state, @klass.with_state
1556
+ end
1557
+
1558
+ def test_should_not_redefine_plural_with_scope
1559
+ assert_equal :with_states, @klass.with_states
1560
+ end
1561
+
1562
+ def test_should_not_redefine_singular_without_scope
1563
+ assert_equal :without_state, @klass.without_state
1564
+ end
1565
+
1566
+ def test_should_not_redefine_plural_without_scope
1567
+ assert_equal :without_states, @klass.without_states
1568
+ end
1569
+
1570
+ def test_should_not_redefine_human_attribute_name_reader
1571
+ assert_equal :human_state_name, @klass.human_state_name
1572
+ end
1573
+
1574
+ def test_should_not_redefine_human_event_name_reader
1575
+ assert_equal :human_state_event_name, @klass.human_state_event_name
1576
+ end
1577
+
1578
+ def test_should_not_redefine_attribute_reader
1579
+ assert_equal 'parked', @object.state
1580
+ end
1581
+
1582
+ def test_should_not_redefine_attribute_writer
1583
+ @object.state = 'parked'
1584
+ assert_equal 'parked', @object.status
1585
+ end
1586
+
1587
+ def test_should_not_define_attribute_predicate
1588
+ assert @object.state?
1589
+ end
1590
+
1591
+ def test_should_not_redefine_attribute_name_reader
1592
+ assert_equal :parked, @object.state_name
1593
+ end
1594
+
1595
+ def test_should_not_redefine_attribute_human_name_reader
1596
+ assert_equal 'parked', @object.human_state_name
1597
+ end
1598
+
1599
+ def test_should_not_redefine_attribute_events_reader
1600
+ assert_equal [:ignite], @object.state_events
1601
+ end
1602
+
1603
+ def test_should_not_redefine_attribute_transitions_reader
1604
+ assert_equal [{:parked => :idling}], @object.state_transitions
1605
+ end
1606
+
1607
+ def test_should_not_redefine_attribute_paths_reader
1608
+ assert_equal [[{:parked => :idling}]], @object.state_paths
1609
+ end
1610
+
1611
+ def test_should_not_redefine_event_runner
1612
+ assert_equal true, @object.fire_state_event
1613
+ end
1614
+
1615
+ def test_should_output_warning
1616
+ expected = [
1617
+ 'Instance method "state_events"',
1618
+ 'Instance method "state_transitions"',
1619
+ 'Instance method "fire_state_event"',
1620
+ 'Instance method "state_paths"',
1621
+ 'Class method "human_state_name"',
1622
+ 'Class method "human_state_event_name"',
1623
+ 'Instance method "state_name"',
1624
+ 'Instance method "human_state_name"',
1625
+ 'Class method "with_state"',
1626
+ 'Class method "with_states"',
1627
+ 'Class method "without_state"',
1628
+ 'Class method "without_states"'
1629
+ ].map {|method| "#{method} is already defined in #{@superclass.to_s}, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true.\n"}.join
1630
+
1631
+ assert_equal expected, $stderr.string
1632
+ end
1633
+
1634
+ def teardown
1635
+ $stderr = @original_stderr
1636
+ StateMachine::Integrations.send(:remove_const, 'Custom')
1637
+ end
1638
+ end
1639
+
1640
+ class MachineWithConflictingHelpersAfterDefinitionTest < Test::Unit::TestCase
1641
+ def setup
1642
+ require 'stringio'
1643
+ @original_stderr, $stderr = $stderr, StringIO.new
1644
+
1645
+ @klass = Class.new do
1646
+ def self.with_state
1647
+ :with_state
1648
+ end
1649
+
1650
+ def self.with_states
1651
+ :with_states
1652
+ end
1653
+
1654
+ def self.without_state
1655
+ :without_state
1656
+ end
1657
+
1658
+ def self.without_states
1659
+ :without_states
1660
+ end
1661
+
1662
+ def self.human_state_name
1663
+ :human_state_name
1664
+ end
1665
+
1666
+ def self.human_state_event_name
1667
+ :human_state_event_name
1668
+ end
1669
+
1670
+ attr_accessor :status
1671
+
1672
+ def state
1673
+ 'parked'
1674
+ end
1675
+
1676
+ def state=(value)
1677
+ self.status = value
1678
+ end
1679
+
1680
+ def state?
1681
+ true
1682
+ end
1683
+
1684
+ def state_name
1685
+ :parked
1686
+ end
1687
+
1688
+ def human_state_name
1689
+ 'parked'
1690
+ end
1691
+
1692
+ def state_events
1693
+ [:ignite]
1694
+ end
1695
+
1696
+ def state_transitions
1697
+ [{:parked => :idling}]
1698
+ end
1699
+
1700
+ def state_paths
1701
+ [[{:parked => :idling}]]
1702
+ end
1703
+
1704
+ def fire_state_event
1705
+ true
1706
+ end
1707
+ end
1708
+
1709
+ StateMachine::Integrations.const_set('Custom', Module.new do
1710
+ include StateMachine::Integrations::Base
1711
+
1712
+ def create_with_scope(name)
1713
+ lambda {|klass, values| []}
1714
+ end
1715
+
1716
+ def create_without_scope(name)
1717
+ lambda {|klass, values| []}
1718
+ end
1719
+ end)
1720
+
1721
+ @machine = StateMachine::Machine.new(@klass, :integration => :custom)
1722
+ @machine.state :parked, :idling
1723
+ @machine.event :ignite
1724
+ @object = @klass.new
1725
+ end
1726
+
1727
+ def test_should_not_redefine_singular_with_scope
1728
+ assert_equal :with_state, @klass.with_state
1729
+ end
1730
+
1731
+ def test_should_not_redefine_plural_with_scope
1732
+ assert_equal :with_states, @klass.with_states
1733
+ end
1734
+
1735
+ def test_should_not_redefine_singular_without_scope
1736
+ assert_equal :without_state, @klass.without_state
1737
+ end
1738
+
1739
+ def test_should_not_redefine_plural_without_scope
1740
+ assert_equal :without_states, @klass.without_states
1741
+ end
1742
+
1743
+ def test_should_not_redefine_human_attribute_name_reader
1744
+ assert_equal :human_state_name, @klass.human_state_name
1745
+ end
1746
+
1747
+ def test_should_not_redefine_human_event_name_reader
1748
+ assert_equal :human_state_event_name, @klass.human_state_event_name
1749
+ end
1750
+
1751
+ def test_should_not_redefine_attribute_reader
1752
+ assert_equal 'parked', @object.state
1753
+ end
1754
+
1755
+ def test_should_not_redefine_attribute_writer
1756
+ @object.state = 'parked'
1757
+ assert_equal 'parked', @object.status
1758
+ end
1759
+
1760
+ def test_should_not_define_attribute_predicate
1761
+ assert @object.state?
1762
+ end
1763
+
1764
+ def test_should_not_redefine_attribute_name_reader
1765
+ assert_equal :parked, @object.state_name
1766
+ end
1767
+
1768
+ def test_should_not_redefine_attribute_human_name_reader
1769
+ assert_equal 'parked', @object.human_state_name
1770
+ end
1771
+
1772
+ def test_should_not_redefine_attribute_events_reader
1773
+ assert_equal [:ignite], @object.state_events
1774
+ end
1775
+
1776
+ def test_should_not_redefine_attribute_transitions_reader
1777
+ assert_equal [{:parked => :idling}], @object.state_transitions
1778
+ end
1779
+
1780
+ def test_should_not_redefine_attribute_paths_reader
1781
+ assert_equal [[{:parked => :idling}]], @object.state_paths
1782
+ end
1783
+
1784
+ def test_should_not_redefine_event_runner
1785
+ assert_equal true, @object.fire_state_event
1786
+ end
1787
+
1788
+ def test_should_allow_super_chaining
1789
+ @klass.class_eval do
1790
+ def self.with_state(*states)
1791
+ super
1792
+ end
1793
+
1794
+ def self.with_states(*states)
1795
+ super
1796
+ end
1797
+
1798
+ def self.without_state(*states)
1799
+ super
1800
+ end
1801
+
1802
+ def self.without_states(*states)
1803
+ super
1804
+ end
1805
+
1806
+ def self.human_state_name(state)
1807
+ super
1808
+ end
1809
+
1810
+ def self.human_state_event_name(event)
1811
+ super
1812
+ end
1813
+
1814
+ attr_accessor :status
1815
+
1816
+ def state
1817
+ super
1818
+ end
1819
+
1820
+ def state=(value)
1821
+ super
1822
+ end
1823
+
1824
+ def state?(state)
1825
+ super
1826
+ end
1827
+
1828
+ def state_name
1829
+ super
1830
+ end
1831
+
1832
+ def human_state_name
1833
+ super
1834
+ end
1835
+
1836
+ def state_events
1837
+ super
1838
+ end
1839
+
1840
+ def state_transitions
1841
+ super
1842
+ end
1843
+
1844
+ def state_paths
1845
+ super
1846
+ end
1847
+
1848
+ def fire_state_event(event)
1849
+ super
1850
+ end
1851
+ end
1852
+
1853
+ assert_equal [], @klass.with_state
1854
+ assert_equal [], @klass.with_states
1855
+ assert_equal [], @klass.without_state
1856
+ assert_equal [], @klass.without_states
1857
+ assert_equal 'parked', @klass.human_state_name(:parked)
1858
+ assert_equal 'ignite', @klass.human_state_event_name(:ignite)
1859
+
1860
+ assert_equal nil, @object.state
1861
+ @object.state = 'idling'
1862
+ assert_equal 'idling', @object.state
1863
+ assert_equal nil, @object.status
1864
+ assert_equal false, @object.state?(:parked)
1865
+ assert_equal :idling, @object.state_name
1866
+ assert_equal 'idling', @object.human_state_name
1867
+ assert_equal [], @object.state_events
1868
+ assert_equal [], @object.state_transitions
1869
+ assert_equal [], @object.state_paths
1870
+ assert_equal false, @object.fire_state_event(:ignite)
1871
+ end
1872
+
1873
+ def test_should_not_output_warning
1874
+ assert_equal '', $stderr.string
1875
+ end
1876
+
1877
+ def teardown
1878
+ $stderr = @original_stderr
1879
+ StateMachine::Integrations.send(:remove_const, 'Custom')
1880
+ end
1881
+ end
1882
+
1883
+ class MachineWithSuperclassConflictingHelpersAfterDefinitionTest < Test::Unit::TestCase
1884
+ def setup
1885
+ require 'stringio'
1886
+ @original_stderr, $stderr = $stderr, StringIO.new
1887
+
1888
+ @superclass = Class.new
1889
+ @klass = Class.new(@superclass)
1890
+
1891
+ @machine = StateMachine::Machine.new(@klass)
1892
+ @machine.state :parked, :idling
1893
+ @machine.event :ignite
1894
+
1895
+ @superclass.class_eval do
1896
+ def state?
1897
+ true
1898
+ end
1899
+ end
1900
+
1901
+ @object = @klass.new
1902
+ end
1903
+
1904
+ def test_should_call_superclass_attribute_predicate_without_arguments
1905
+ assert @object.state?
1906
+ end
1907
+
1908
+ def test_should_define_attribute_predicate_with_arguments
1909
+ assert !@object.state?(:parked)
1910
+ end
1911
+
1912
+ def teardown
1913
+ $stderr = @original_stderr
1914
+ end
1915
+ end
1916
+
1917
+ class MachineWithoutInitializeTest < Test::Unit::TestCase
1918
+ def setup
1919
+ @klass = Class.new
1920
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1921
+ @object = @klass.new
1922
+ end
1923
+
1924
+ def test_should_initialize_state
1925
+ assert_equal 'parked', @object.state
1926
+ end
1927
+ end
1928
+
1929
+ class MachineWithInitializeWithoutSuperTest < Test::Unit::TestCase
1930
+ def setup
1931
+ @klass = Class.new do
1932
+ def initialize
1933
+ end
1934
+ end
1935
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1936
+ @object = @klass.new
1937
+ end
1938
+
1939
+ def test_should_not_initialize_state
1940
+ assert_nil @object.state
1941
+ end
1942
+ end
1943
+
1944
+ class MachineWithInitializeAndSuperTest < Test::Unit::TestCase
1945
+ def setup
1946
+ @klass = Class.new do
1947
+ def initialize
1948
+ super()
1949
+ end
1950
+ end
1951
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1952
+ @object = @klass.new
1953
+ end
1954
+
1955
+ def test_should_initialize_state
1956
+ assert_equal 'parked', @object.state
1957
+ end
1958
+ end
1959
+
1960
+ class MachineWithInitializeArgumentsAndBlockTest < Test::Unit::TestCase
1961
+ def setup
1962
+ @superclass = Class.new do
1963
+ attr_reader :args
1964
+ attr_reader :block_given
1965
+
1966
+ def initialize(*args)
1967
+ @args = args
1968
+ @block_given = block_given?
1969
+ end
1970
+ end
1971
+ @klass = Class.new(@superclass)
1972
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1973
+ @object = @klass.new(1, 2, 3) {}
1974
+ end
1975
+
1976
+ def test_should_initialize_state
1977
+ assert_equal 'parked', @object.state
1978
+ end
1979
+
1980
+ def test_should_preserve_arguments
1981
+ assert_equal [1, 2, 3], @object.args
1982
+ end
1983
+
1984
+ def test_should_preserve_block
1985
+ assert @object.block_given
1986
+ end
1987
+ end
1988
+
1989
+ class MachineWithCustomInitializeTest < Test::Unit::TestCase
1990
+ def setup
1991
+ @klass = Class.new do
1992
+ def initialize(state = nil, options = {})
1993
+ @state = state
1994
+ initialize_state_machines(options)
1995
+ end
1996
+ end
1997
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1998
+ @object = @klass.new
1999
+ end
2000
+
2001
+ def test_should_initialize_state
2002
+ assert_equal 'parked', @object.state
2003
+ end
2004
+
2005
+ def test_should_allow_custom_options
2006
+ @machine.state :idling
2007
+ @object = @klass.new('idling', :static => :force)
2008
+ assert_equal 'parked', @object.state
2009
+ end
2010
+ end
2011
+
2012
+ class MachinePersistenceTest < Test::Unit::TestCase
2013
+ def setup
2014
+ @klass = Class.new do
2015
+ attr_accessor :state_event
2016
+ end
2017
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2018
+ @object = @klass.new
2019
+ end
2020
+
2021
+ def test_should_allow_reading_state
2022
+ assert_equal 'parked', @machine.read(@object, :state)
2023
+ end
2024
+
2025
+ def test_should_allow_reading_custom_attributes
2026
+ assert_nil @machine.read(@object, :event)
2027
+
2028
+ @object.state_event = 'ignite'
2029
+ assert_equal 'ignite', @machine.read(@object, :event)
2030
+ end
2031
+
2032
+ def test_should_allow_reading_custom_instance_variables
2033
+ @klass.class_eval do
2034
+ attr_writer :state_value
2035
+ end
2036
+
2037
+ @object.state_value = 1
2038
+ assert_raise(NoMethodError) { @machine.read(@object, :value) }
2039
+ assert_equal 1, @machine.read(@object, :value, true)
2040
+ end
2041
+
2042
+ def test_should_allow_writing_state
2043
+ @machine.write(@object, :state, 'idling')
2044
+ assert_equal 'idling', @object.state
2045
+ end
2046
+
2047
+ def test_should_allow_writing_custom_attributes
2048
+ @machine.write(@object, :event, 'ignite')
2049
+ assert_equal 'ignite', @object.state_event
2050
+ end
2051
+
2052
+ def test_should_allow_writing_custom_instance_variables
2053
+ @klass.class_eval do
2054
+ attr_reader :state_value
2055
+ end
2056
+
2057
+ assert_raise(NoMethodError) { @machine.write(@object, :value, 1) }
2058
+ assert_equal 1, @machine.write(@object, :value, 1, true)
2059
+ assert_equal 1, @object.state_value
2060
+ end
2061
+ end
2062
+
2063
+
2064
+ class MachineWithStatesTest < Test::Unit::TestCase
2065
+ def setup
2066
+ @klass = Class.new
2067
+ @machine = StateMachine::Machine.new(@klass)
2068
+ @parked, @idling = @machine.state :parked, :idling
2069
+
2070
+ @object = @klass.new
2071
+ end
2072
+
2073
+ def test_should_have_states
2074
+ assert_equal [nil, :parked, :idling], @machine.states.map {|state| state.name}
2075
+ end
2076
+
2077
+ def test_should_allow_state_lookup_by_name
2078
+ assert_equal @parked, @machine.states[:parked]
2079
+ end
2080
+
2081
+ def test_should_allow_state_lookup_by_value
2082
+ assert_equal @parked, @machine.states['parked', :value]
2083
+ end
2084
+
2085
+ def test_should_allow_human_state_name_lookup
2086
+ assert_equal 'parked', @klass.human_state_name(:parked)
2087
+ end
2088
+
2089
+ def test_should_raise_exception_on_invalid_human_state_name_lookup
2090
+ exception = assert_raise(IndexError) {@klass.human_state_name(:invalid)}
2091
+ assert_equal ':invalid is an invalid name', exception.message
2092
+ end
2093
+
2094
+ def test_should_use_stringified_name_for_value
2095
+ assert_equal 'parked', @parked.value
2096
+ end
2097
+
2098
+ def test_should_not_use_custom_matcher
2099
+ assert_nil @parked.matcher
2100
+ end
2101
+
2102
+ def test_should_raise_exception_if_invalid_option_specified
2103
+ exception = assert_raise(ArgumentError) {@machine.state(:first_gear, :invalid => true)}
2104
+ assert_equal 'Invalid key(s): invalid', exception.message
2105
+ end
2106
+
2107
+ def test_should_raise_exception_if_conflicting_type_used_for_name
2108
+ exception = assert_raise(ArgumentError) { @machine.state 'first_gear' }
2109
+ assert_equal '"first_gear" state defined as String, :parked defined as Symbol; all states must be consistent', exception.message
2110
+ end
2111
+
2112
+ def test_should_not_raise_exception_if_conflicting_type_is_nil_for_name
2113
+ assert_nothing_raised { @machine.state nil }
2114
+ end
2115
+ end
2116
+
2117
+ class MachineWithStatesWithCustomValuesTest < Test::Unit::TestCase
2118
+ def setup
2119
+ @klass = Class.new
2120
+ @machine = StateMachine::Machine.new(@klass)
2121
+ @state = @machine.state :parked, :value => 1
2122
+
2123
+ @object = @klass.new
2124
+ @object.state = 1
2125
+ end
2126
+
2127
+ def test_should_use_custom_value
2128
+ assert_equal 1, @state.value
2129
+ end
2130
+
2131
+ def test_should_allow_lookup_by_custom_value
2132
+ assert_equal @state, @machine.states[1, :value]
2133
+ end
2134
+ end
2135
+
2136
+ class MachineWithStatesWithCustomHumanNamesTest < Test::Unit::TestCase
2137
+ def setup
2138
+ @klass = Class.new
2139
+ @machine = StateMachine::Machine.new(@klass)
2140
+ @state = @machine.state :parked, :human_name => 'stopped'
2141
+ end
2142
+
2143
+ def test_should_use_custom_human_name
2144
+ assert_equal 'stopped', @state.human_name
2145
+ end
2146
+
2147
+ def test_should_allow_human_state_name_lookup
2148
+ assert_equal 'stopped', @klass.human_state_name(:parked)
2149
+ end
2150
+ end
2151
+
2152
+ class MachineWithStatesWithRuntimeDependenciesTest < Test::Unit::TestCase
2153
+ def setup
2154
+ @klass = Class.new
2155
+ @machine = StateMachine::Machine.new(@klass)
2156
+ @machine.state :parked
2157
+ end
2158
+
2159
+ def test_should_not_evaluate_value_during_definition
2160
+ assert_nothing_raised { @machine.state :parked, :value => lambda {raise ArgumentError} }
2161
+ end
2162
+
2163
+ def test_should_not_evaluate_if_not_initial_state
2164
+ @machine.state :parked, :value => lambda {raise ArgumentError}
2165
+ assert_nothing_raised { @klass.new }
2166
+ end
2167
+ end
2168
+
2169
+ class MachineWithStateWithMatchersTest < Test::Unit::TestCase
2170
+ def setup
2171
+ @klass = Class.new
2172
+ @machine = StateMachine::Machine.new(@klass)
2173
+ @state = @machine.state :parked, :if => lambda {|value| !value.nil?}
2174
+
2175
+ @object = @klass.new
2176
+ @object.state = 1
2177
+ end
2178
+
2179
+ def test_should_use_custom_matcher
2180
+ assert_not_nil @state.matcher
2181
+ assert @state.matches?(1)
2182
+ assert !@state.matches?(nil)
2183
+ end
2184
+ end
2185
+
2186
+ class MachineWithCachedStateTest < Test::Unit::TestCase
2187
+ def setup
2188
+ @klass = Class.new
2189
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2190
+ @state = @machine.state :parked, :value => lambda {Object.new}, :cache => true
2191
+
2192
+ @object = @klass.new
2193
+ end
2194
+
2195
+ def test_should_use_evaluated_value
2196
+ assert_instance_of Object, @object.state
2197
+ end
2198
+
2199
+ def test_use_same_value_across_multiple_objects
2200
+ assert_equal @object.state, @klass.new.state
2201
+ end
2202
+ end
2203
+
2204
+ class MachineWithStatesWithBehaviorsTest < Test::Unit::TestCase
2205
+ def setup
2206
+ @klass = Class.new
2207
+ @machine = StateMachine::Machine.new(@klass)
2208
+
2209
+ @parked, @idling = @machine.state :parked, :idling do
2210
+ def speed
2211
+ 0
2212
+ end
2213
+ end
2214
+ end
2215
+
2216
+ def test_should_define_behaviors_for_each_state
2217
+ assert_not_nil @parked.context_methods[:speed]
2218
+ assert_not_nil @idling.context_methods[:speed]
2219
+ end
2220
+
2221
+ def test_should_define_different_behaviors_for_each_state
2222
+ assert_not_equal @parked.context_methods[:speed], @idling.context_methods[:speed]
2223
+ end
2224
+ end
2225
+
2226
+ class MachineWithExistingStateTest < Test::Unit::TestCase
2227
+ def setup
2228
+ @klass = Class.new
2229
+ @machine = StateMachine::Machine.new(@klass)
2230
+ @state = @machine.state :parked
2231
+ @same_state = @machine.state :parked, :value => 1
2232
+ end
2233
+
2234
+ def test_should_not_create_a_new_state
2235
+ assert_same @state, @same_state
2236
+ end
2237
+
2238
+ def test_should_update_attributes
2239
+ assert_equal 1, @state.value
2240
+ end
2241
+
2242
+ def test_should_no_longer_be_able_to_look_up_state_by_original_value
2243
+ assert_nil @machine.states['parked', :value]
2244
+ end
2245
+
2246
+ def test_should_be_able_to_look_up_state_by_new_value
2247
+ assert_equal @state, @machine.states[1, :value]
2248
+ end
2249
+ end
2250
+
2251
+ class MachineWithStateMatchersTest < Test::Unit::TestCase
2252
+ def setup
2253
+ @klass = Class.new
2254
+ @machine = StateMachine::Machine.new(@klass)
2255
+ end
2256
+
2257
+ def test_should_empty_array_for_all_matcher
2258
+ assert_equal [], @machine.state(StateMachine::AllMatcher.instance)
2259
+ end
2260
+
2261
+ def test_should_return_referenced_states_for_blacklist_matcher
2262
+ assert_instance_of StateMachine::State, @machine.state(StateMachine::BlacklistMatcher.new([:parked]))
2263
+ end
2264
+
2265
+ def test_should_not_allow_configurations
2266
+ exception = assert_raise(ArgumentError) { @machine.state(StateMachine::BlacklistMatcher.new([:parked]), :human_name => 'Parked') }
2267
+ assert_equal 'Cannot configure states when using matchers (using {:human_name=>"Parked"})', exception.message
2268
+ end
2269
+
2270
+ def test_should_track_referenced_states
2271
+ @machine.state(StateMachine::BlacklistMatcher.new([:parked]))
2272
+ assert_equal [nil, :parked], @machine.states.map {|state| state.name}
2273
+ end
2274
+
2275
+ def test_should_eval_context_for_matching_states
2276
+ contexts_run = []
2277
+ @machine.event(StateMachine::BlacklistMatcher.new([:parked])) { contexts_run << self.name }
2278
+
2279
+ @machine.event :parked
2280
+ assert_equal [], contexts_run
2281
+
2282
+ @machine.event :idling
2283
+ assert_equal [:idling], contexts_run
2284
+
2285
+ @machine.event :first_gear, :second_gear
2286
+ assert_equal [:idling, :first_gear, :second_gear], contexts_run
2287
+ end
2288
+ end
2289
+
2290
+ class MachineWithOtherStates < Test::Unit::TestCase
2291
+ def setup
2292
+ @klass = Class.new
2293
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2294
+ @parked, @idling = @machine.other_states(:parked, :idling)
2295
+ end
2296
+
2297
+ def test_should_include_other_states_in_known_states
2298
+ assert_equal [@parked, @idling], @machine.states.to_a
2299
+ end
2300
+
2301
+ def test_should_use_default_value
2302
+ assert_equal 'idling', @idling.value
2303
+ end
2304
+
2305
+ def test_should_not_create_matcher
2306
+ assert_nil @idling.matcher
2307
+ end
2308
+ end
2309
+
2310
+ class MachineWithEventsTest < Test::Unit::TestCase
2311
+ def setup
2312
+ @klass = Class.new
2313
+ @machine = StateMachine::Machine.new(@klass)
2314
+ end
2315
+
2316
+ def test_should_return_the_created_event
2317
+ assert_instance_of StateMachine::Event, @machine.event(:ignite)
2318
+ end
2319
+
2320
+ def test_should_create_event_with_given_name
2321
+ event = @machine.event(:ignite) {}
2322
+ assert_equal :ignite, event.name
2323
+ end
2324
+
2325
+ def test_should_evaluate_block_within_event_context
2326
+ responded = false
2327
+ @machine.event :ignite do
2328
+ responded = respond_to?(:transition)
2329
+ end
2330
+
2331
+ assert responded
2332
+ end
2333
+
2334
+ def test_should_be_aliased_as_on
2335
+ event = @machine.on(:ignite) {}
2336
+ assert_equal :ignite, event.name
2337
+ end
2338
+
2339
+ def test_should_have_events
2340
+ event = @machine.event(:ignite)
2341
+ assert_equal [event], @machine.events.to_a
2342
+ end
2343
+
2344
+ def test_should_allow_human_state_name_lookup
2345
+ @machine.event(:ignite)
2346
+ assert_equal 'ignite', @klass.human_state_event_name(:ignite)
2347
+ end
2348
+
2349
+ def test_should_raise_exception_on_invalid_human_state_event_name_lookup
2350
+ exception = assert_raise(IndexError) {@klass.human_state_event_name(:invalid)}
2351
+ assert_equal ':invalid is an invalid name', exception.message
2352
+ end
2353
+
2354
+ def test_should_raise_exception_if_conflicting_type_used_for_name
2355
+ @machine.event :park
2356
+ exception = assert_raise(ArgumentError) { @machine.event 'ignite' }
2357
+ assert_equal '"ignite" event defined as String, :park defined as Symbol; all events must be consistent', exception.message
2358
+ end
2359
+ end
2360
+
2361
+ class MachineWithExistingEventTest < Test::Unit::TestCase
2362
+ def setup
2363
+ @machine = StateMachine::Machine.new(Class.new)
2364
+ @event = @machine.event(:ignite)
2365
+ @same_event = @machine.event(:ignite)
2366
+ end
2367
+
2368
+ def test_should_not_create_new_event
2369
+ assert_same @event, @same_event
2370
+ end
2371
+
2372
+ def test_should_allow_accessing_event_without_block
2373
+ assert_equal @event, @machine.event(:ignite)
2374
+ end
2375
+ end
2376
+
2377
+ class MachineWithEventsWithCustomHumanNamesTest < Test::Unit::TestCase
2378
+ def setup
2379
+ @klass = Class.new
2380
+ @machine = StateMachine::Machine.new(@klass)
2381
+ @event = @machine.event(:ignite, :human_name => 'start')
2382
+ end
2383
+
2384
+ def test_should_use_custom_human_name
2385
+ assert_equal 'start', @event.human_name
2386
+ end
2387
+
2388
+ def test_should_allow_human_state_name_lookup
2389
+ assert_equal 'start', @klass.human_state_event_name(:ignite)
2390
+ end
2391
+ end
2392
+
2393
+ class MachineWithEventMatchersTest < Test::Unit::TestCase
2394
+ def setup
2395
+ @klass = Class.new
2396
+ @machine = StateMachine::Machine.new(@klass)
2397
+ end
2398
+
2399
+ def test_should_empty_array_for_all_matcher
2400
+ assert_equal [], @machine.event(StateMachine::AllMatcher.instance)
2401
+ end
2402
+
2403
+ def test_should_return_referenced_events_for_blacklist_matcher
2404
+ assert_instance_of StateMachine::Event, @machine.event(StateMachine::BlacklistMatcher.new([:park]))
2405
+ end
2406
+
2407
+ def test_should_not_allow_configurations
2408
+ exception = assert_raise(ArgumentError) { @machine.event(StateMachine::BlacklistMatcher.new([:park]), :human_name => 'Park') }
2409
+ assert_equal 'Cannot configure events when using matchers (using {:human_name=>"Park"})', exception.message
2410
+ end
2411
+
2412
+ def test_should_track_referenced_events
2413
+ @machine.event(StateMachine::BlacklistMatcher.new([:park]))
2414
+ assert_equal [:park], @machine.events.map {|event| event.name}
2415
+ end
2416
+
2417
+ def test_should_eval_context_for_matching_events
2418
+ contexts_run = []
2419
+ @machine.event(StateMachine::BlacklistMatcher.new([:park])) { contexts_run << self.name }
2420
+
2421
+ @machine.event :park
2422
+ assert_equal [], contexts_run
2423
+
2424
+ @machine.event :ignite
2425
+ assert_equal [:ignite], contexts_run
2426
+
2427
+ @machine.event :shift_up, :shift_down
2428
+ assert_equal [:ignite, :shift_up, :shift_down], contexts_run
2429
+ end
2430
+ end
2431
+
2432
+ class MachineWithEventsWithTransitionsTest < Test::Unit::TestCase
2433
+ def setup
2434
+ @klass = Class.new
2435
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2436
+ @event = @machine.event(:ignite) do
2437
+ transition :parked => :idling
2438
+ transition :stalled => :idling
2439
+ end
2440
+ end
2441
+
2442
+ def test_should_have_events
2443
+ assert_equal [@event], @machine.events.to_a
2444
+ end
2445
+
2446
+ def test_should_track_states_defined_in_event_transitions
2447
+ assert_equal [:parked, :idling, :stalled], @machine.states.map {|state| state.name}
2448
+ end
2449
+
2450
+ def test_should_not_duplicate_states_defined_in_multiple_event_transitions
2451
+ @machine.event :park do
2452
+ transition :idling => :parked
2453
+ end
2454
+
2455
+ assert_equal [:parked, :idling, :stalled], @machine.states.map {|state| state.name}
2456
+ end
2457
+
2458
+ def test_should_track_state_from_new_events
2459
+ @machine.event :shift_up do
2460
+ transition :idling => :first_gear
2461
+ end
2462
+
2463
+ assert_equal [:parked, :idling, :stalled, :first_gear], @machine.states.map {|state| state.name}
2464
+ end
2465
+ end
2466
+
2467
+ class MachineWithMultipleEventsTest < Test::Unit::TestCase
2468
+ def setup
2469
+ @klass = Class.new
2470
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2471
+ @park, @shift_down = @machine.event(:park, :shift_down) do
2472
+ transition :first_gear => :parked
2473
+ end
2474
+ end
2475
+
2476
+ def test_should_have_events
2477
+ assert_equal [@park, @shift_down], @machine.events.to_a
2478
+ end
2479
+
2480
+ def test_should_define_transitions_for_each_event
2481
+ [@park, @shift_down].each {|event| assert_equal 1, event.branches.size}
2482
+ end
2483
+
2484
+ def test_should_transition_the_same_for_each_event
2485
+ object = @klass.new
2486
+ object.state = 'first_gear'
2487
+ object.park
2488
+ assert_equal 'parked', object.state
2489
+
2490
+ object = @klass.new
2491
+ object.state = 'first_gear'
2492
+ object.shift_down
2493
+ assert_equal 'parked', object.state
2494
+ end
2495
+ end
2496
+
2497
+ class MachineWithTransitionsTest < Test::Unit::TestCase
2498
+ def setup
2499
+ @klass = Class.new
2500
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2501
+ end
2502
+
2503
+ def test_should_require_on_event
2504
+ exception = assert_raise(ArgumentError) { @machine.transition(:parked => :idling) }
2505
+ assert_equal 'Must specify :on event', exception.message
2506
+ end
2507
+
2508
+ def test_should_not_allow_except_on_option
2509
+ exception = assert_raise(ArgumentError) {@machine.transition(:except_on => :ignite, :on => :ignite)}
2510
+ assert_equal 'Invalid key(s): except_on', exception.message
2511
+ end
2512
+
2513
+ def test_should_allow_transitioning_without_a_to_state
2514
+ assert_nothing_raised {@machine.transition(:from => :parked, :on => :ignite)}
2515
+ end
2516
+
2517
+ def test_should_allow_transitioning_without_a_from_state
2518
+ assert_nothing_raised {@machine.transition(:to => :idling, :on => :ignite)}
2519
+ end
2520
+
2521
+ def test_should_allow_except_from_option
2522
+ assert_nothing_raised {@machine.transition(:except_from => :idling, :on => :ignite)}
2523
+ end
2524
+
2525
+ def test_should_allow_except_to_option
2526
+ assert_nothing_raised {@machine.transition(:except_to => :parked, :on => :ignite)}
2527
+ end
2528
+
2529
+ def test_should_allow_implicit_options
2530
+ branch = @machine.transition(:first_gear => :second_gear, :on => :shift_up)
2531
+ assert_instance_of StateMachine::Branch, branch
2532
+
2533
+ state_requirements = branch.state_requirements
2534
+ assert_equal 1, state_requirements.length
2535
+
2536
+ assert_instance_of StateMachine::WhitelistMatcher, state_requirements[0][:from]
2537
+ assert_equal [:first_gear], state_requirements[0][:from].values
2538
+ assert_instance_of StateMachine::WhitelistMatcher, state_requirements[0][:to]
2539
+ assert_equal [:second_gear], state_requirements[0][:to].values
2540
+ assert_instance_of StateMachine::WhitelistMatcher, branch.event_requirement
2541
+ assert_equal [:shift_up], branch.event_requirement.values
2542
+ end
2543
+
2544
+ def test_should_allow_multiple_implicit_options
2545
+ branch = @machine.transition(:first_gear => :second_gear, :second_gear => :third_gear, :on => :shift_up)
2546
+
2547
+ state_requirements = branch.state_requirements
2548
+ assert_equal 2, state_requirements.length
2549
+ end
2550
+
2551
+ def test_should_allow_verbose_options
2552
+ branch = @machine.transition(:from => :parked, :to => :idling, :on => :ignite)
2553
+ assert_instance_of StateMachine::Branch, branch
2554
+ end
2555
+
2556
+ def test_should_include_all_transition_states_in_machine_states
2557
+ @machine.transition(:parked => :idling, :on => :ignite)
2558
+
2559
+ assert_equal [:parked, :idling], @machine.states.map {|state| state.name}
2560
+ end
2561
+
2562
+ def test_should_include_all_transition_events_in_machine_events
2563
+ @machine.transition(:parked => :idling, :on => :ignite)
2564
+
2565
+ assert_equal [:ignite], @machine.events.map {|event| event.name}
2566
+ end
2567
+
2568
+ def test_should_allow_multiple_events
2569
+ branches = @machine.transition(:parked => :ignite, :on => [:ignite, :shift_up])
2570
+
2571
+ assert_equal 2, branches.length
2572
+ assert_equal [:ignite, :shift_up], @machine.events.map {|event| event.name}
2573
+ end
2574
+
2575
+ def test_should_not_modify_options
2576
+ options = {:parked => :idling, :on => :ignite}
2577
+ @machine.transition(options)
2578
+
2579
+ assert_equal options, {:parked => :idling, :on => :ignite}
2580
+ end
2581
+ end
2582
+
2583
+ class MachineWithTransitionCallbacksTest < Test::Unit::TestCase
2584
+ def setup
2585
+ @klass = Class.new do
2586
+ attr_accessor :callbacks
2587
+ end
2588
+
2589
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2590
+ @event = @machine.event :ignite do
2591
+ transition :parked => :idling
2592
+ end
2593
+
2594
+ @object = @klass.new
2595
+ @object.callbacks = []
2596
+ end
2597
+
2598
+ def test_should_not_raise_exception_if_implicit_option_specified
2599
+ assert_nothing_raised {@machine.before_transition :invalid => :valid, :do => lambda {}}
2600
+ end
2601
+
2602
+ def test_should_raise_exception_if_method_not_specified
2603
+ exception = assert_raise(ArgumentError) {@machine.before_transition :to => :idling}
2604
+ assert_equal 'Method(s) for callback must be specified', exception.message
2605
+ end
2606
+
2607
+ def test_should_invoke_callbacks_during_transition
2608
+ @machine.before_transition lambda {|object| object.callbacks << 'before'}
2609
+ @machine.after_transition lambda {|object| object.callbacks << 'after'}
2610
+ @machine.around_transition lambda {|object, transition, block| object.callbacks << 'before_around'; block.call; object.callbacks << 'after_around'}
2611
+
2612
+ @event.fire(@object)
2613
+ assert_equal %w(before before_around after_around after), @object.callbacks
2614
+ end
2615
+
2616
+ def test_should_allow_multiple_callbacks
2617
+ @machine.before_transition lambda {|object| object.callbacks << 'before1'}, lambda {|object| object.callbacks << 'before2'}
2618
+ @machine.after_transition lambda {|object| object.callbacks << 'after1'}, lambda {|object| object.callbacks << 'after2'}
2619
+ @machine.around_transition(
2620
+ lambda {|object, transition, block| object.callbacks << 'before_around1'; block.call; object.callbacks << 'after_around1'},
2621
+ lambda {|object, transition, block| object.callbacks << 'before_around2'; block.call; object.callbacks << 'after_around2'}
2622
+ )
2623
+
2624
+ @event.fire(@object)
2625
+ assert_equal %w(before1 before2 before_around1 before_around2 after_around2 after_around1 after1 after2), @object.callbacks
2626
+ end
2627
+
2628
+ def test_should_allow_multiple_callbacks_with_requirements
2629
+ @machine.before_transition lambda {|object| object.callbacks << 'before_parked1'}, lambda {|object| object.callbacks << 'before_parked2'}, :from => :parked
2630
+ @machine.before_transition lambda {|object| object.callbacks << 'before_idling1'}, lambda {|object| object.callbacks << 'before_idling2'}, :from => :idling
2631
+ @machine.after_transition lambda {|object| object.callbacks << 'after_parked1'}, lambda {|object| object.callbacks << 'after_parked2'}, :from => :parked
2632
+ @machine.after_transition lambda {|object| object.callbacks << 'after_idling1'}, lambda {|object| object.callbacks << 'after_idling2'}, :from => :idling
2633
+ @machine.around_transition(
2634
+ lambda {|object, transition, block| object.callbacks << 'before_around_parked1'; block.call; object.callbacks << 'after_around_parked1'},
2635
+ lambda {|object, transition, block| object.callbacks << 'before_around_parked2'; block.call; object.callbacks << 'after_around_parked2'},
2636
+ :from => :parked
2637
+ )
2638
+ @machine.around_transition(
2639
+ lambda {|object, transition, block| object.callbacks << 'before_around_idling1'; block.call; object.callbacks << 'after_around_idling1'},
2640
+ lambda {|object, transition, block| object.callbacks << 'before_around_idling2'; block.call; object.callbacks << 'after_around_idling2'},
2641
+ :from => :idling
2642
+ )
2643
+
2644
+ @event.fire(@object)
2645
+ assert_equal %w(before_parked1 before_parked2 before_around_parked1 before_around_parked2 after_around_parked2 after_around_parked1 after_parked1 after_parked2), @object.callbacks
2646
+ end
2647
+
2648
+ def test_should_support_from_requirement
2649
+ @machine.before_transition :from => :parked, :do => lambda {|object| object.callbacks << :parked}
2650
+ @machine.before_transition :from => :idling, :do => lambda {|object| object.callbacks << :idling}
2651
+
2652
+ @event.fire(@object)
2653
+ assert_equal [:parked], @object.callbacks
2654
+ end
2655
+
2656
+ def test_should_support_except_from_requirement
2657
+ @machine.before_transition :except_from => :parked, :do => lambda {|object| object.callbacks << :parked}
2658
+ @machine.before_transition :except_from => :idling, :do => lambda {|object| object.callbacks << :idling}
2659
+
2660
+ @event.fire(@object)
2661
+ assert_equal [:idling], @object.callbacks
2662
+ end
2663
+
2664
+ def test_should_support_to_requirement
2665
+ @machine.before_transition :to => :parked, :do => lambda {|object| object.callbacks << :parked}
2666
+ @machine.before_transition :to => :idling, :do => lambda {|object| object.callbacks << :idling}
2667
+
2668
+ @event.fire(@object)
2669
+ assert_equal [:idling], @object.callbacks
2670
+ end
2671
+
2672
+ def test_should_support_except_to_requirement
2673
+ @machine.before_transition :except_to => :parked, :do => lambda {|object| object.callbacks << :parked}
2674
+ @machine.before_transition :except_to => :idling, :do => lambda {|object| object.callbacks << :idling}
2675
+
2676
+ @event.fire(@object)
2677
+ assert_equal [:parked], @object.callbacks
2678
+ end
2679
+
2680
+ def test_should_support_on_requirement
2681
+ @machine.before_transition :on => :park, :do => lambda {|object| object.callbacks << :park}
2682
+ @machine.before_transition :on => :ignite, :do => lambda {|object| object.callbacks << :ignite}
2683
+
2684
+ @event.fire(@object)
2685
+ assert_equal [:ignite], @object.callbacks
2686
+ end
2687
+
2688
+ def test_should_support_except_on_requirement
2689
+ @machine.before_transition :except_on => :park, :do => lambda {|object| object.callbacks << :park}
2690
+ @machine.before_transition :except_on => :ignite, :do => lambda {|object| object.callbacks << :ignite}
2691
+
2692
+ @event.fire(@object)
2693
+ assert_equal [:park], @object.callbacks
2694
+ end
2695
+
2696
+ def test_should_support_implicit_requirement
2697
+ @machine.before_transition :parked => :idling, :do => lambda {|object| object.callbacks << :parked}
2698
+ @machine.before_transition :idling => :parked, :do => lambda {|object| object.callbacks << :idling}
2699
+
2700
+ @event.fire(@object)
2701
+ assert_equal [:parked], @object.callbacks
2702
+ end
2703
+
2704
+ def test_should_track_states_defined_in_transition_callbacks
2705
+ @machine.before_transition :parked => :idling, :do => lambda {}
2706
+ @machine.after_transition :first_gear => :second_gear, :do => lambda {}
2707
+ @machine.around_transition :third_gear => :fourth_gear, :do => lambda {}
2708
+
2709
+ assert_equal [:parked, :idling, :first_gear, :second_gear, :third_gear, :fourth_gear], @machine.states.map {|state| state.name}
2710
+ end
2711
+
2712
+ def test_should_not_duplicate_states_defined_in_multiple_event_transitions
2713
+ @machine.before_transition :parked => :idling, :do => lambda {}
2714
+ @machine.after_transition :first_gear => :second_gear, :do => lambda {}
2715
+ @machine.after_transition :parked => :idling, :do => lambda {}
2716
+ @machine.around_transition :parked => :idling, :do => lambda {}
2717
+
2718
+ assert_equal [:parked, :idling, :first_gear, :second_gear], @machine.states.map {|state| state.name}
2719
+ end
2720
+
2721
+ def test_should_define_predicates_for_each_state
2722
+ [:parked?, :idling?].each {|predicate| assert @object.respond_to?(predicate)}
2723
+ end
2724
+ end
2725
+
2726
+ class MachineWithFailureCallbacksTest < Test::Unit::TestCase
2727
+ def setup
2728
+ @klass = Class.new do
2729
+ attr_accessor :callbacks
2730
+ end
2731
+
2732
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2733
+ @event = @machine.event :ignite
2734
+
2735
+ @object = @klass.new
2736
+ @object.callbacks = []
2737
+ end
2738
+
2739
+ def test_should_raise_exception_if_implicit_option_specified
2740
+ exception = assert_raise(ArgumentError) {@machine.after_failure :invalid => :valid, :do => lambda {}}
2741
+ assert_equal 'Invalid key(s): invalid', exception.message
2742
+ end
2743
+
2744
+ def test_should_raise_exception_if_method_not_specified
2745
+ exception = assert_raise(ArgumentError) {@machine.after_failure :on => :ignite}
2746
+ assert_equal 'Method(s) for callback must be specified', exception.message
2747
+ end
2748
+
2749
+ def test_should_invoke_callbacks_during_failed_transition
2750
+ @machine.after_failure lambda {|object| object.callbacks << 'failure'}
2751
+
2752
+ @event.fire(@object)
2753
+ assert_equal %w(failure), @object.callbacks
2754
+ end
2755
+
2756
+ def test_should_allow_multiple_callbacks
2757
+ @machine.after_failure lambda {|object| object.callbacks << 'failure1'}, lambda {|object| object.callbacks << 'failure2'}
2758
+
2759
+ @event.fire(@object)
2760
+ assert_equal %w(failure1 failure2), @object.callbacks
2761
+ end
2762
+
2763
+ def test_should_allow_multiple_callbacks_with_requirements
2764
+ @machine.after_failure lambda {|object| object.callbacks << 'failure_ignite1'}, lambda {|object| object.callbacks << 'failure_ignite2'}, :on => :ignite
2765
+ @machine.after_failure lambda {|object| object.callbacks << 'failure_park1'}, lambda {|object| object.callbacks << 'failure_park2'}, :on => :park
2766
+
2767
+ @event.fire(@object)
2768
+ assert_equal %w(failure_ignite1 failure_ignite2), @object.callbacks
2769
+ end
2770
+ end
2771
+
2772
+ class MachineWithPathsTest < Test::Unit::TestCase
2773
+ def setup
2774
+ @klass = Class.new
2775
+ @machine = StateMachine::Machine.new(@klass)
2776
+ @machine.event :ignite do
2777
+ transition :parked => :idling
2778
+ end
2779
+ @machine.event :shift_up do
2780
+ transition :first_gear => :second_gear
2781
+ end
2782
+
2783
+ @object = @klass.new
2784
+ @object.state = 'parked'
2785
+ end
2786
+
2787
+ def test_should_have_paths
2788
+ assert_equal [[StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)]], @machine.paths_for(@object)
2789
+ end
2790
+
2791
+ def test_should_allow_requirement_configuration
2792
+ assert_equal [[StateMachine::Transition.new(@object, @machine, :shift_up, :first_gear, :second_gear)]], @machine.paths_for(@object, :from => :first_gear)
2793
+ end
2794
+ end
2795
+
2796
+ class MachineWithOwnerSubclassTest < Test::Unit::TestCase
2797
+ def setup
2798
+ @klass = Class.new
2799
+ @machine = StateMachine::Machine.new(@klass)
2800
+ @subclass = Class.new(@klass)
2801
+ end
2802
+
2803
+ def test_should_have_a_different_collection_of_state_machines
2804
+ assert_not_same @klass.state_machines, @subclass.state_machines
2805
+ end
2806
+
2807
+ def test_should_have_the_same_attribute_associated_state_machines
2808
+ assert_equal @klass.state_machines, @subclass.state_machines
2809
+ end
2810
+ end
2811
+
2812
+ class MachineWithExistingMachinesOnOwnerClassTest < Test::Unit::TestCase
2813
+ def setup
2814
+ @klass = Class.new
2815
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2816
+ @second_machine = StateMachine::Machine.new(@klass, :status, :initial => :idling)
2817
+ @object = @klass.new
2818
+ end
2819
+
2820
+ def test_should_track_each_state_machine
2821
+ expected = {:state => @machine, :status => @second_machine}
2822
+ assert_equal expected, @klass.state_machines
2823
+ end
2824
+
2825
+ def test_should_initialize_state_for_both_machines
2826
+ assert_equal 'parked', @object.state
2827
+ assert_equal 'idling', @object.status
2828
+ end
2829
+ end
2830
+
2831
+ class MachineWithExistingMachinesWithSameAttributesOnOwnerClassTest < Test::Unit::TestCase
2832
+ def setup
2833
+ @klass = Class.new
2834
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2835
+ @second_machine = StateMachine::Machine.new(@klass, :public_state, :initial => :idling, :attribute => :state)
2836
+ @object = @klass.new
2837
+ end
2838
+
2839
+ def test_should_track_each_state_machine
2840
+ expected = {:state => @machine, :public_state => @second_machine}
2841
+ assert_equal expected, @klass.state_machines
2842
+ end
2843
+
2844
+ def test_should_write_to_state_only_once
2845
+ @klass.class_eval do
2846
+ attr_reader :write_count
2847
+
2848
+ def state=(value)
2849
+ @write_count ||= 0
2850
+ @write_count += 1
2851
+ end
2852
+ end
2853
+ object = @klass.new
2854
+
2855
+ assert_equal 1, object.write_count
2856
+ end
2857
+
2858
+ def test_should_initialize_based_on_first_machine
2859
+ assert_equal 'parked', @object.state
2860
+ end
2861
+
2862
+ def test_should_not_allow_second_machine_to_initialize_state
2863
+ @object.state = nil
2864
+ @second_machine.initialize_state(@object)
2865
+ assert_nil @object.state
2866
+ end
2867
+
2868
+ def test_should_allow_transitions_on_both_machines
2869
+ @machine.event :ignite do
2870
+ transition :parked => :idling
2871
+ end
2872
+
2873
+ @second_machine.event :park do
2874
+ transition :idling => :parked
2875
+ end
2876
+
2877
+ @object.ignite
2878
+ assert_equal 'idling', @object.state
2879
+
2880
+ @object.park
2881
+ assert_equal 'parked', @object.state
2882
+ end
2883
+
2884
+ def test_should_copy_new_states_to_sibling_machines
2885
+ @first_gear = @machine.state :first_gear
2886
+ assert_equal @first_gear, @second_machine.state(:first_gear)
2887
+
2888
+ @second_gear = @second_machine.state :second_gear
2889
+ assert_equal @second_gear, @machine.state(:second_gear)
2890
+ end
2891
+
2892
+ def test_should_copy_all_existing_states_to_new_machines
2893
+ third_machine = StateMachine::Machine.new(@klass, :protected_state, :attribute => :state)
2894
+
2895
+ assert_equal @machine.state(:parked), third_machine.state(:parked)
2896
+ assert_equal @machine.state(:idling), third_machine.state(:idling)
2897
+ end
2898
+ end
2899
+
2900
+ class MachineWithExistingMachinesWithSameAttributesOnOwnerSubclassTest < Test::Unit::TestCase
2901
+ def setup
2902
+ @klass = Class.new
2903
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2904
+ @second_machine = StateMachine::Machine.new(@klass, :public_state, :initial => :idling, :attribute => :state)
2905
+
2906
+ @subclass = Class.new(@klass)
2907
+ @object = @subclass.new
2908
+ end
2909
+
2910
+ def test_should_not_copy_sibling_machines_to_subclass_after_initialization
2911
+ @subclass.state_machine(:state) {}
2912
+ assert_equal @klass.state_machine(:public_state), @subclass.state_machine(:public_state)
2913
+ end
2914
+
2915
+ def test_should_copy_sibling_machines_to_subclass_after_new_state
2916
+ subclass_machine = @subclass.state_machine(:state) {}
2917
+ subclass_machine.state :first_gear
2918
+ assert_not_equal @klass.state_machine(:public_state), @subclass.state_machine(:public_state)
2919
+ end
2920
+
2921
+ def test_should_copy_new_states_to_sibling_machines
2922
+ subclass_machine = @subclass.state_machine(:state) {}
2923
+ @first_gear = subclass_machine.state :first_gear
2924
+
2925
+ second_subclass_machine = @subclass.state_machine(:public_state)
2926
+ assert_equal @first_gear, second_subclass_machine.state(:first_gear)
2927
+ end
2928
+ end
2929
+
2930
+ class MachineWithNamespaceTest < Test::Unit::TestCase
2931
+ def setup
2932
+ @klass = Class.new
2933
+ @machine = StateMachine::Machine.new(@klass, :namespace => 'alarm', :initial => :active) do
2934
+ event :enable do
2935
+ transition :off => :active
2936
+ end
2937
+
2938
+ event :disable do
2939
+ transition :active => :off
2940
+ end
2941
+ end
2942
+ @object = @klass.new
2943
+ end
2944
+
2945
+ def test_should_namespace_state_predicates
2946
+ [:alarm_active?, :alarm_off?].each do |name|
2947
+ assert @object.respond_to?(name)
2948
+ end
2949
+ end
2950
+
2951
+ def test_should_namespace_event_checks
2952
+ [:can_enable_alarm?, :can_disable_alarm?].each do |name|
2953
+ assert @object.respond_to?(name)
2954
+ end
2955
+ end
2956
+
2957
+ def test_should_namespace_event_transition_readers
2958
+ [:enable_alarm_transition, :disable_alarm_transition].each do |name|
2959
+ assert @object.respond_to?(name)
2960
+ end
2961
+ end
2962
+
2963
+ def test_should_namespace_events
2964
+ [:enable_alarm, :disable_alarm].each do |name|
2965
+ assert @object.respond_to?(name)
2966
+ end
2967
+ end
2968
+
2969
+ def test_should_namespace_bang_events
2970
+ [:enable_alarm!, :disable_alarm!].each do |name|
2971
+ assert @object.respond_to?(name)
2972
+ end
2973
+ end
2974
+ end
2975
+
2976
+ class MachineWithCustomAttributeTest < Test::Unit::TestCase
2977
+ def setup
2978
+ StateMachine::Integrations.const_set('Custom', Module.new do
2979
+ include StateMachine::Integrations::Base
2980
+
2981
+ @defaults = {:action => :save, :use_transactions => false}
2982
+
2983
+ def create_with_scope(name)
2984
+ lambda {}
2985
+ end
2986
+
2987
+ def create_without_scope(name)
2988
+ lambda {}
2989
+ end
2990
+ end)
2991
+
2992
+ @klass = Class.new
2993
+ @machine = StateMachine::Machine.new(@klass, :state, :attribute => :state_id, :initial => :active, :integration => :custom) do
2994
+ event :ignite do
2995
+ transition :parked => :idling
2996
+ end
2997
+ end
2998
+ @object = @klass.new
2999
+ end
3000
+
3001
+ def test_should_define_a_reader_attribute_for_the_attribute
3002
+ assert @object.respond_to?(:state_id)
3003
+ end
3004
+
3005
+ def test_should_define_a_writer_attribute_for_the_attribute
3006
+ assert @object.respond_to?(:state_id=)
3007
+ end
3008
+
3009
+ def test_should_define_a_predicate_for_the_attribute
3010
+ assert @object.respond_to?(:state?)
3011
+ end
3012
+
3013
+ def test_should_define_a_name_reader_for_the_attribute
3014
+ assert @object.respond_to?(:state_name)
3015
+ end
3016
+
3017
+ def test_should_define_a_human_name_reader_for_the_attribute
3018
+ assert @object.respond_to?(:state_name)
3019
+ end
3020
+
3021
+ def test_should_define_an_event_reader_for_the_attribute
3022
+ assert @object.respond_to?(:state_events)
3023
+ end
3024
+
3025
+ def test_should_define_a_transition_reader_for_the_attribute
3026
+ assert @object.respond_to?(:state_transitions)
3027
+ end
3028
+
3029
+ def test_should_define_a_path_reader_for_the_attribute
3030
+ assert @object.respond_to?(:state_paths)
3031
+ end
3032
+
3033
+ def test_should_define_an_event_runner_for_the_attribute
3034
+ assert @object.respond_to?(:fire_state_event)
3035
+ end
3036
+
3037
+ def test_should_define_a_human_attribute_name_reader
3038
+ assert @klass.respond_to?(:human_state_name)
3039
+ end
3040
+
3041
+ def test_should_define_a_human_event_name_reader
3042
+ assert @klass.respond_to?(:human_state_event_name)
3043
+ end
3044
+
3045
+ def test_should_define_singular_with_scope
3046
+ assert @klass.respond_to?(:with_state)
3047
+ end
3048
+
3049
+ def test_should_define_singular_without_scope
3050
+ assert @klass.respond_to?(:without_state)
3051
+ end
3052
+
3053
+ def test_should_define_plural_with_scope
3054
+ assert @klass.respond_to?(:with_states)
3055
+ end
3056
+
3057
+ def test_should_define_plural_without_scope
3058
+ assert @klass.respond_to?(:without_states)
3059
+ end
3060
+
3061
+ def test_should_define_state_machines_reader
3062
+ expected = {:state => @machine}
3063
+ assert_equal expected, @klass.state_machines
3064
+ end
3065
+
3066
+ def teardown
3067
+ StateMachine::Integrations.send(:remove_const, 'Custom')
3068
+ end
3069
+ end
3070
+
3071
+ class MachineFinderWithoutExistingMachineTest < Test::Unit::TestCase
3072
+ def setup
3073
+ @klass = Class.new
3074
+ @machine = StateMachine::Machine.find_or_create(@klass)
3075
+ end
3076
+
3077
+ def test_should_accept_a_block
3078
+ called = false
3079
+ StateMachine::Machine.find_or_create(Class.new) do
3080
+ called = respond_to?(:event)
3081
+ end
3082
+
3083
+ assert called
3084
+ end
3085
+
3086
+ def test_should_create_a_new_machine
3087
+ assert_not_nil @machine
3088
+ end
3089
+
3090
+ def test_should_use_default_state
3091
+ assert_equal :state, @machine.attribute
3092
+ end
3093
+ end
3094
+
3095
+ class MachineFinderWithExistingOnSameClassTest < Test::Unit::TestCase
3096
+ def setup
3097
+ @klass = Class.new
3098
+ @existing_machine = StateMachine::Machine.new(@klass)
3099
+ @machine = StateMachine::Machine.find_or_create(@klass)
3100
+ end
3101
+
3102
+ def test_should_accept_a_block
3103
+ called = false
3104
+ StateMachine::Machine.find_or_create(@klass) do
3105
+ called = respond_to?(:event)
3106
+ end
3107
+
3108
+ assert called
3109
+ end
3110
+
3111
+ def test_should_not_create_a_new_machine
3112
+ assert_same @machine, @existing_machine
3113
+ end
3114
+ end
3115
+
3116
+ class MachineFinderWithExistingMachineOnSuperclassTest < Test::Unit::TestCase
3117
+ def setup
3118
+ integration = Module.new do
3119
+ include StateMachine::Integrations::Base
3120
+
3121
+ def self.matches?(klass)
3122
+ false
3123
+ end
3124
+ end
3125
+ StateMachine::Integrations.const_set('Custom', integration)
3126
+
3127
+ @base_class = Class.new
3128
+ @base_machine = StateMachine::Machine.new(@base_class, :status, :action => :save, :integration => :custom)
3129
+ @base_machine.event(:ignite) {}
3130
+ @base_machine.before_transition(lambda {})
3131
+ @base_machine.after_transition(lambda {})
3132
+ @base_machine.around_transition(lambda {})
3133
+
3134
+ @klass = Class.new(@base_class)
3135
+ @machine = StateMachine::Machine.find_or_create(@klass, :status) {}
3136
+ end
3137
+
3138
+ def test_should_accept_a_block
3139
+ called = false
3140
+ StateMachine::Machine.find_or_create(Class.new(@base_class)) do
3141
+ called = respond_to?(:event)
3142
+ end
3143
+
3144
+ assert called
3145
+ end
3146
+
3147
+ def test_should_not_create_a_new_machine_if_no_block_or_options
3148
+ machine = StateMachine::Machine.find_or_create(Class.new(@base_class), :status)
3149
+
3150
+ assert_same machine, @base_machine
3151
+ end
3152
+
3153
+ def test_should_create_a_new_machine_if_given_options
3154
+ machine = StateMachine::Machine.find_or_create(@klass, :status, :initial => :parked)
3155
+
3156
+ assert_not_nil machine
3157
+ assert_not_same machine, @base_machine
3158
+ end
3159
+
3160
+ def test_should_create_a_new_machine_if_given_block
3161
+ assert_not_nil @machine
3162
+ assert_not_same @machine, @base_machine
3163
+ end
3164
+
3165
+ def test_should_copy_the_base_attribute
3166
+ assert_equal :status, @machine.attribute
3167
+ end
3168
+
3169
+ def test_should_copy_the_base_configuration
3170
+ assert_equal :save, @machine.action
3171
+ end
3172
+
3173
+ def test_should_copy_events
3174
+ # Can't assert equal arrays since their machines change
3175
+ assert_equal 1, @machine.events.length
3176
+ end
3177
+
3178
+ def test_should_copy_before_callbacks
3179
+ assert_equal @base_machine.callbacks[:before], @machine.callbacks[:before]
3180
+ end
3181
+
3182
+ def test_should_copy_after_transitions
3183
+ assert_equal @base_machine.callbacks[:after], @machine.callbacks[:after]
3184
+ end
3185
+
3186
+ def test_should_use_the_same_integration
3187
+ assert((class << @machine; ancestors; end).include?(StateMachine::Integrations::Custom))
3188
+ end
3189
+
3190
+ def teardown
3191
+ StateMachine::Integrations.send(:remove_const, 'Custom')
3192
+ end
3193
+ end
3194
+
3195
+ class MachineFinderCustomOptionsTest < Test::Unit::TestCase
3196
+ def setup
3197
+ @klass = Class.new
3198
+ @machine = StateMachine::Machine.find_or_create(@klass, :status, :initial => :parked)
3199
+ @object = @klass.new
3200
+ end
3201
+
3202
+ def test_should_use_custom_attribute
3203
+ assert_equal :status, @machine.attribute
3204
+ end
3205
+
3206
+ def test_should_set_custom_initial_state
3207
+ assert_equal :parked, @machine.initial_state(@object).name
3208
+ end
3209
+ end
3210
+
3211
+ begin
3212
+ # Load library
3213
+ require 'graphviz'
3214
+
3215
+ class MachineDrawingTest < Test::Unit::TestCase
3216
+ def setup
3217
+ @klass = Class.new do
3218
+ def self.name; @name ||= "Vehicle_#{rand(1000000)}"; end
3219
+ end
3220
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
3221
+ @machine.event :ignite do
3222
+ transition :parked => :idling
3223
+ end
3224
+ end
3225
+
3226
+ def test_should_raise_exception_if_invalid_option_specified
3227
+ assert_raise(ArgumentError) {@machine.draw(:invalid => true)}
3228
+ end
3229
+
3230
+ def test_should_save_file_with_class_name_by_default
3231
+ @machine.draw
3232
+ assert File.exists?("./#{@klass.name}_state.png")
3233
+ end
3234
+
3235
+ def test_should_allow_base_name_to_be_customized
3236
+ name = "machine_#{rand(1000000)}"
3237
+ @machine.draw(:name => name)
3238
+ @path = "./#{name}.png"
3239
+ assert File.exists?(@path)
3240
+ end
3241
+
3242
+ def test_should_allow_format_to_be_customized
3243
+ @machine.draw(:format => 'jpg')
3244
+ @path = "./#{@klass.name}_state.jpg"
3245
+ assert File.exists?(@path)
3246
+ end
3247
+
3248
+ def test_should_allow_path_to_be_customized
3249
+ @machine.draw(:path => "#{File.dirname(__FILE__)}/")
3250
+ @path = "#{File.dirname(__FILE__)}/#{@klass.name}_state.png"
3251
+ assert File.exists?(@path)
3252
+ end
3253
+
3254
+ def test_should_allow_orientation_to_be_landscape
3255
+ graph = @machine.draw(:orientation => 'landscape')
3256
+ assert_equal 'LR', graph['rankdir'].to_s.gsub('"', '')
3257
+ end
3258
+
3259
+ def test_should_allow_orientation_to_be_portrait
3260
+ graph = @machine.draw(:orientation => 'portrait')
3261
+ assert_equal 'TB', graph['rankdir'].to_s.gsub('"', '')
3262
+ end
3263
+
3264
+ if Constants::RGV_VERSION != '0.9.0'
3265
+ def test_should_allow_human_names_to_be_displayed
3266
+ @machine.event :ignite, :human_name => 'Ignite'
3267
+ @machine.state :parked, :human_name => 'Parked'
3268
+ @machine.state :idling, :human_name => 'Idling'
3269
+ graph = @machine.draw(:human_names => true)
3270
+
3271
+ parked_node = graph.get_node('parked')
3272
+ assert_equal 'Parked', parked_node['label'].to_s.gsub('"', '')
3273
+
3274
+ idling_node = graph.get_node('idling')
3275
+ assert_equal 'Idling', idling_node['label'].to_s.gsub('"', '')
3276
+ end
3277
+ end
3278
+
3279
+ def teardown
3280
+ FileUtils.rm Dir[@path || "./#{@klass.name}_state.png"]
3281
+ end
3282
+ end
3283
+
3284
+ class MachineDrawingWithIntegerStatesTest < Test::Unit::TestCase
3285
+ def setup
3286
+ @klass = Class.new do
3287
+ def self.name; @name ||= "Vehicle_#{rand(1000000)}"; end
3288
+ end
3289
+ @machine = StateMachine::Machine.new(@klass, :state_id, :initial => :parked)
3290
+ @machine.event :ignite do
3291
+ transition :parked => :idling
3292
+ end
3293
+ @machine.state :parked, :value => 1
3294
+ @machine.state :idling, :value => 2
3295
+ @graph = @machine.draw
3296
+ end
3297
+
3298
+ def test_should_draw_all_states
3299
+ assert_equal 3, @graph.node_count
3300
+ end
3301
+
3302
+ def test_should_draw_all_events
3303
+ assert_equal 2, @graph.edge_count
3304
+ end
3305
+
3306
+ def test_should_draw_machine
3307
+ assert File.exist?("./#{@klass.name}_state_id.png")
3308
+ end
3309
+
3310
+ def teardown
3311
+ FileUtils.rm Dir["./#{@klass.name}_state_id.png"]
3312
+ end
3313
+ end
3314
+
3315
+ class MachineDrawingWithNilStatesTest < Test::Unit::TestCase
3316
+ def setup
3317
+ @klass = Class.new do
3318
+ def self.name; @name ||= "Vehicle_#{rand(1000000)}"; end
3319
+ end
3320
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
3321
+ @machine.event :ignite do
3322
+ transition :parked => :idling
3323
+ end
3324
+ @machine.state :parked, :value => nil
3325
+ @graph = @machine.draw
3326
+ end
3327
+
3328
+ def test_should_draw_all_states
3329
+ assert_equal 3, @graph.node_count
3330
+ end
3331
+
3332
+ def test_should_draw_all_events
3333
+ assert_equal 2, @graph.edge_count
3334
+ end
3335
+
3336
+ def test_should_draw_machine
3337
+ assert File.exist?("./#{@klass.name}_state.png")
3338
+ end
3339
+
3340
+ def teardown
3341
+ FileUtils.rm Dir["./#{@klass.name}_state.png"]
3342
+ end
3343
+ end
3344
+
3345
+ class MachineDrawingWithDynamicStatesTest < Test::Unit::TestCase
3346
+ def setup
3347
+ @klass = Class.new do
3348
+ def self.name; @name ||= "Vehicle_#{rand(1000000)}"; end
3349
+ end
3350
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
3351
+ @machine.event :activate do
3352
+ transition :parked => :idling
3353
+ end
3354
+ @machine.state :idling, :value => lambda {Time.now}
3355
+ @graph = @machine.draw
3356
+ end
3357
+
3358
+ def test_should_draw_all_states
3359
+ assert_equal 3, @graph.node_count
3360
+ end
3361
+
3362
+ def test_should_draw_all_events
3363
+ assert_equal 2, @graph.edge_count
3364
+ end
3365
+
3366
+ def test_should_draw_machine
3367
+ assert File.exist?("./#{@klass.name}_state.png")
3368
+ end
3369
+
3370
+ def teardown
3371
+ FileUtils.rm Dir["./#{@klass.name}_state.png"]
3372
+ end
3373
+ end
3374
+
3375
+ class MachineClassDrawingTest < Test::Unit::TestCase
3376
+ def setup
3377
+ @klass = Class.new do
3378
+ def self.name; @name ||= "Vehicle_#{rand(1000000)}"; end
3379
+ end
3380
+ @machine = StateMachine::Machine.new(@klass)
3381
+ @machine.event :ignite do
3382
+ transition :parked => :idling
3383
+ end
3384
+ end
3385
+
3386
+ def test_should_raise_exception_if_no_class_names_specified
3387
+ exception = assert_raise(ArgumentError) {StateMachine::Machine.draw(nil)}
3388
+ assert_equal 'At least one class must be specified', exception.message
3389
+ end
3390
+
3391
+ def test_should_load_files
3392
+ StateMachine::Machine.draw('Switch', :file => File.expand_path("#{File.dirname(__FILE__)}/../files/switch.rb"))
3393
+ assert defined?(::Switch)
3394
+ end
3395
+
3396
+ def test_should_allow_path_and_format_to_be_customized
3397
+ StateMachine::Machine.draw('Switch', :file => File.expand_path("#{File.dirname(__FILE__)}/../files/switch.rb"), :path => "#{File.dirname(__FILE__)}/", :format => 'jpg')
3398
+ assert File.exist?("#{File.dirname(__FILE__)}/#{Switch.name}_state.jpg")
3399
+ end
3400
+
3401
+ def teardown
3402
+ FileUtils.rm Dir["{.,#{File.dirname(__FILE__)}}/#{Switch.name}_state.{jpg,png}"]
3403
+ end
3404
+ end
3405
+ rescue LoadError
3406
+ $stderr.puts 'Skipping GraphViz StateMachine::Machine tests. `gem install ruby-graphviz` >= v0.9.17 and try again.'
3407
+ end unless ENV['TRAVIS']