state_machine_updated_for_ruby_3_2 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (308) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.travis.yml +72 -0
  4. data/.yardopts +5 -0
  5. data/Appraisals +491 -0
  6. data/CHANGELOG.md +506 -0
  7. data/Dockerfile +13 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE +20 -0
  10. data/README.md +1244 -0
  11. data/Rakefile +41 -0
  12. data/examples/AutoShop_state.png +0 -0
  13. data/examples/Car_state.png +0 -0
  14. data/examples/Gemfile +5 -0
  15. data/examples/Gemfile.lock +14 -0
  16. data/examples/TrafficLight_state.png +0 -0
  17. data/examples/Vehicle_state.png +0 -0
  18. data/examples/auto_shop.rb +13 -0
  19. data/examples/car.rb +21 -0
  20. data/examples/doc/AutoShop.html +2856 -0
  21. data/examples/doc/AutoShop_state.png +0 -0
  22. data/examples/doc/Car.html +919 -0
  23. data/examples/doc/Car_state.png +0 -0
  24. data/examples/doc/TrafficLight.html +2230 -0
  25. data/examples/doc/TrafficLight_state.png +0 -0
  26. data/examples/doc/Vehicle.html +7921 -0
  27. data/examples/doc/Vehicle_state.png +0 -0
  28. data/examples/doc/_index.html +136 -0
  29. data/examples/doc/class_list.html +47 -0
  30. data/examples/doc/css/common.css +1 -0
  31. data/examples/doc/css/full_list.css +55 -0
  32. data/examples/doc/css/style.css +322 -0
  33. data/examples/doc/file_list.html +46 -0
  34. data/examples/doc/frames.html +13 -0
  35. data/examples/doc/index.html +136 -0
  36. data/examples/doc/js/app.js +205 -0
  37. data/examples/doc/js/full_list.js +173 -0
  38. data/examples/doc/js/jquery.js +16 -0
  39. data/examples/doc/method_list.html +734 -0
  40. data/examples/doc/top-level-namespace.html +105 -0
  41. data/examples/merb-rest/controller.rb +51 -0
  42. data/examples/merb-rest/model.rb +28 -0
  43. data/examples/merb-rest/view_edit.html.erb +24 -0
  44. data/examples/merb-rest/view_index.html.erb +23 -0
  45. data/examples/merb-rest/view_new.html.erb +13 -0
  46. data/examples/merb-rest/view_show.html.erb +17 -0
  47. data/examples/rails-rest/controller.rb +43 -0
  48. data/examples/rails-rest/migration.rb +7 -0
  49. data/examples/rails-rest/model.rb +23 -0
  50. data/examples/rails-rest/view__form.html.erb +34 -0
  51. data/examples/rails-rest/view_edit.html.erb +6 -0
  52. data/examples/rails-rest/view_index.html.erb +25 -0
  53. data/examples/rails-rest/view_new.html.erb +5 -0
  54. data/examples/rails-rest/view_show.html.erb +19 -0
  55. data/examples/traffic_light.rb +9 -0
  56. data/examples/vehicle.rb +33 -0
  57. data/gemfiles/active_model_3.0.0.gemfile +7 -0
  58. data/gemfiles/active_model_3.0.0.gemfile.lock +35 -0
  59. data/gemfiles/active_model_3.0.5.gemfile +7 -0
  60. data/gemfiles/active_model_3.0.5.gemfile.lock +35 -0
  61. data/gemfiles/active_model_3.1.1.gemfile +7 -0
  62. data/gemfiles/active_model_3.1.1.gemfile.lock +36 -0
  63. data/gemfiles/active_model_3.2.1.gemfile +7 -0
  64. data/gemfiles/active_model_3.2.12.gemfile +7 -0
  65. data/gemfiles/active_model_3.2.12.gemfile.lock +36 -0
  66. data/gemfiles/active_model_3.2.13.rc1.gemfile +7 -0
  67. data/gemfiles/active_model_3.2.13.rc1.gemfile.lock +36 -0
  68. data/gemfiles/active_model_4.0.0.gemfile +9 -0
  69. data/gemfiles/active_model_4.0.0.gemfile.lock +78 -0
  70. data/gemfiles/active_record_2.0.0.gemfile +9 -0
  71. data/gemfiles/active_record_2.0.0.gemfile.lock +39 -0
  72. data/gemfiles/active_record_2.0.5.gemfile +9 -0
  73. data/gemfiles/active_record_2.0.5.gemfile.lock +39 -0
  74. data/gemfiles/active_record_2.1.0.gemfile +9 -0
  75. data/gemfiles/active_record_2.1.0.gemfile.lock +39 -0
  76. data/gemfiles/active_record_2.1.2.gemfile +9 -0
  77. data/gemfiles/active_record_2.1.2.gemfile.lock +39 -0
  78. data/gemfiles/active_record_2.2.3.gemfile +9 -0
  79. data/gemfiles/active_record_2.2.3.gemfile.lock +39 -0
  80. data/gemfiles/active_record_2.3.12.gemfile +9 -0
  81. data/gemfiles/active_record_2.3.12.gemfile.lock +39 -0
  82. data/gemfiles/active_record_2.3.5.gemfile +9 -0
  83. data/gemfiles/active_record_2.3.5.gemfile.lock +39 -0
  84. data/gemfiles/active_record_3.0.0.gemfile +9 -0
  85. data/gemfiles/active_record_3.0.0.gemfile.lock +51 -0
  86. data/gemfiles/active_record_3.0.5.gemfile +9 -0
  87. data/gemfiles/active_record_3.0.5.gemfile.lock +50 -0
  88. data/gemfiles/active_record_3.1.1.gemfile +9 -0
  89. data/gemfiles/active_record_3.1.1.gemfile.lock +51 -0
  90. data/gemfiles/active_record_3.2.12.gemfile +9 -0
  91. data/gemfiles/active_record_3.2.12.gemfile.lock +51 -0
  92. data/gemfiles/active_record_3.2.13.rc1.gemfile +9 -0
  93. data/gemfiles/active_record_3.2.13.rc1.gemfile.lock +51 -0
  94. data/gemfiles/active_record_4.0.0.gemfile +11 -0
  95. data/gemfiles/active_record_4.0.0.gemfile.lock +83 -0
  96. data/gemfiles/data_mapper_0.10.2.gemfile +13 -0
  97. data/gemfiles/data_mapper_0.10.2.gemfile.lock +56 -0
  98. data/gemfiles/data_mapper_0.9.11.gemfile +13 -0
  99. data/gemfiles/data_mapper_0.9.11.gemfile.lock +71 -0
  100. data/gemfiles/data_mapper_0.9.4.gemfile +12 -0
  101. data/gemfiles/data_mapper_0.9.4.gemfile.lock +70 -0
  102. data/gemfiles/data_mapper_0.9.7.gemfile +13 -0
  103. data/gemfiles/data_mapper_0.9.7.gemfile.lock +67 -0
  104. data/gemfiles/data_mapper_1.0.0.gemfile +12 -0
  105. data/gemfiles/data_mapper_1.0.0.gemfile.lock +63 -0
  106. data/gemfiles/data_mapper_1.0.1.gemfile +12 -0
  107. data/gemfiles/data_mapper_1.0.1.gemfile.lock +63 -0
  108. data/gemfiles/data_mapper_1.0.2.gemfile +12 -0
  109. data/gemfiles/data_mapper_1.0.2.gemfile.lock +63 -0
  110. data/gemfiles/data_mapper_1.1.0.gemfile +12 -0
  111. data/gemfiles/data_mapper_1.1.0.gemfile.lock +61 -0
  112. data/gemfiles/data_mapper_1.2.0.gemfile +12 -0
  113. data/gemfiles/data_mapper_1.2.0.gemfile.lock +61 -0
  114. data/gemfiles/default.gemfile +7 -0
  115. data/gemfiles/default.gemfile.lock +27 -0
  116. data/gemfiles/graphviz_0.9.17.gemfile +7 -0
  117. data/gemfiles/graphviz_0.9.17.gemfile.lock +29 -0
  118. data/gemfiles/graphviz_0.9.21.gemfile +7 -0
  119. data/gemfiles/graphviz_0.9.21.gemfile.lock +29 -0
  120. data/gemfiles/graphviz_1.0.0.gemfile +7 -0
  121. data/gemfiles/graphviz_1.0.0.gemfile.lock +29 -0
  122. data/gemfiles/graphviz_1.0.3.gemfile +7 -0
  123. data/gemfiles/graphviz_1.0.3.gemfile.lock +29 -0
  124. data/gemfiles/graphviz_1.0.8.gemfile +7 -0
  125. data/gemfiles/graphviz_1.0.8.gemfile.lock +29 -0
  126. data/gemfiles/mongo_mapper_0.10.0.gemfile +8 -0
  127. data/gemfiles/mongo_mapper_0.10.0.gemfile.lock +47 -0
  128. data/gemfiles/mongo_mapper_0.11.2.gemfile +9 -0
  129. data/gemfiles/mongo_mapper_0.11.2.gemfile.lock +48 -0
  130. data/gemfiles/mongo_mapper_0.12.0.gemfile +9 -0
  131. data/gemfiles/mongo_mapper_0.12.0.gemfile.lock +48 -0
  132. data/gemfiles/mongo_mapper_0.5.5.gemfile +8 -0
  133. data/gemfiles/mongo_mapper_0.5.5.gemfile.lock +36 -0
  134. data/gemfiles/mongo_mapper_0.5.8.gemfile +8 -0
  135. data/gemfiles/mongo_mapper_0.5.8.gemfile.lock +36 -0
  136. data/gemfiles/mongo_mapper_0.6.0.gemfile +8 -0
  137. data/gemfiles/mongo_mapper_0.6.0.gemfile.lock +36 -0
  138. data/gemfiles/mongo_mapper_0.6.10.gemfile +8 -0
  139. data/gemfiles/mongo_mapper_0.6.10.gemfile.lock +36 -0
  140. data/gemfiles/mongo_mapper_0.7.0.gemfile +8 -0
  141. data/gemfiles/mongo_mapper_0.7.0.gemfile.lock +36 -0
  142. data/gemfiles/mongo_mapper_0.7.5.gemfile +8 -0
  143. data/gemfiles/mongo_mapper_0.7.5.gemfile.lock +39 -0
  144. data/gemfiles/mongo_mapper_0.8.0.gemfile +10 -0
  145. data/gemfiles/mongo_mapper_0.8.0.gemfile.lock +43 -0
  146. data/gemfiles/mongo_mapper_0.8.3.gemfile +10 -0
  147. data/gemfiles/mongo_mapper_0.8.3.gemfile.lock +43 -0
  148. data/gemfiles/mongo_mapper_0.8.4.gemfile +8 -0
  149. data/gemfiles/mongo_mapper_0.8.4.gemfile.lock +42 -0
  150. data/gemfiles/mongo_mapper_0.8.6.gemfile +8 -0
  151. data/gemfiles/mongo_mapper_0.8.6.gemfile.lock +42 -0
  152. data/gemfiles/mongo_mapper_0.9.0.gemfile +7 -0
  153. data/gemfiles/mongo_mapper_0.9.0.gemfile.lock +45 -0
  154. data/gemfiles/mongoid_2.0.0.gemfile +9 -0
  155. data/gemfiles/mongoid_2.0.0.gemfile.lock +49 -0
  156. data/gemfiles/mongoid_2.1.4.gemfile +9 -0
  157. data/gemfiles/mongoid_2.1.4.gemfile.lock +47 -0
  158. data/gemfiles/mongoid_2.2.4.gemfile +9 -0
  159. data/gemfiles/mongoid_2.2.4.gemfile.lock +47 -0
  160. data/gemfiles/mongoid_2.3.3.gemfile +9 -0
  161. data/gemfiles/mongoid_2.3.3.gemfile.lock +47 -0
  162. data/gemfiles/mongoid_2.4.0.gemfile +9 -0
  163. data/gemfiles/mongoid_2.4.0.gemfile.lock +47 -0
  164. data/gemfiles/mongoid_2.4.10.gemfile +9 -0
  165. data/gemfiles/mongoid_2.4.10.gemfile.lock +47 -0
  166. data/gemfiles/mongoid_2.5.2.gemfile +9 -0
  167. data/gemfiles/mongoid_2.5.2.gemfile.lock +47 -0
  168. data/gemfiles/mongoid_2.6.0.gemfile +9 -0
  169. data/gemfiles/mongoid_2.6.0.gemfile.lock +47 -0
  170. data/gemfiles/mongoid_3.0.0.gemfile +8 -0
  171. data/gemfiles/mongoid_3.0.0.gemfile.lock +45 -0
  172. data/gemfiles/mongoid_3.0.22.gemfile +8 -0
  173. data/gemfiles/mongoid_3.0.22.gemfile.lock +45 -0
  174. data/gemfiles/mongoid_3.1.0.gemfile +8 -0
  175. data/gemfiles/mongoid_3.1.0.gemfile.lock +45 -0
  176. data/gemfiles/sequel_2.11.0.gemfile +9 -0
  177. data/gemfiles/sequel_2.11.0.gemfile.lock +33 -0
  178. data/gemfiles/sequel_2.12.0.gemfile +9 -0
  179. data/gemfiles/sequel_2.12.0.gemfile.lock +33 -0
  180. data/gemfiles/sequel_2.8.0.gemfile +9 -0
  181. data/gemfiles/sequel_2.8.0.gemfile.lock +33 -0
  182. data/gemfiles/sequel_3.0.0.gemfile +9 -0
  183. data/gemfiles/sequel_3.0.0.gemfile.lock +33 -0
  184. data/gemfiles/sequel_3.10.0.gemfile +9 -0
  185. data/gemfiles/sequel_3.10.0.gemfile.lock +33 -0
  186. data/gemfiles/sequel_3.13.0.gemfile +9 -0
  187. data/gemfiles/sequel_3.13.0.gemfile.lock +33 -0
  188. data/gemfiles/sequel_3.14.0.gemfile +9 -0
  189. data/gemfiles/sequel_3.14.0.gemfile.lock +33 -0
  190. data/gemfiles/sequel_3.23.0.gemfile +9 -0
  191. data/gemfiles/sequel_3.23.0.gemfile.lock +33 -0
  192. data/gemfiles/sequel_3.24.0.gemfile +9 -0
  193. data/gemfiles/sequel_3.24.0.gemfile.lock +33 -0
  194. data/gemfiles/sequel_3.29.0.gemfile +9 -0
  195. data/gemfiles/sequel_3.29.0.gemfile.lock +33 -0
  196. data/gemfiles/sequel_3.34.0.gemfile +9 -0
  197. data/gemfiles/sequel_3.34.0.gemfile.lock +33 -0
  198. data/gemfiles/sequel_3.35.0.gemfile +9 -0
  199. data/gemfiles/sequel_3.35.0.gemfile.lock +33 -0
  200. data/gemfiles/sequel_3.4.0.gemfile +9 -0
  201. data/gemfiles/sequel_3.4.0.gemfile.lock +33 -0
  202. data/gemfiles/sequel_3.44.0.gemfile +9 -0
  203. data/gemfiles/sequel_3.44.0.gemfile.lock +33 -0
  204. data/init.rb +1 -0
  205. data/lib/state_machine/assertions.rb +36 -0
  206. data/lib/state_machine/branch.rb +225 -0
  207. data/lib/state_machine/callback.rb +236 -0
  208. data/lib/state_machine/core.rb +12 -0
  209. data/lib/state_machine/core_ext/class/state_machine.rb +5 -0
  210. data/lib/state_machine/core_ext.rb +2 -0
  211. data/lib/state_machine/error.rb +13 -0
  212. data/lib/state_machine/eval_helpers.rb +87 -0
  213. data/lib/state_machine/event.rb +257 -0
  214. data/lib/state_machine/event_collection.rb +141 -0
  215. data/lib/state_machine/extensions.rb +149 -0
  216. data/lib/state_machine/graph.rb +92 -0
  217. data/lib/state_machine/helper_module.rb +17 -0
  218. data/lib/state_machine/initializers/merb.rb +1 -0
  219. data/lib/state_machine/initializers/rails.rb +25 -0
  220. data/lib/state_machine/initializers.rb +4 -0
  221. data/lib/state_machine/integrations/active_model/locale.rb +11 -0
  222. data/lib/state_machine/integrations/active_model/observer.rb +33 -0
  223. data/lib/state_machine/integrations/active_model/observer_update.rb +42 -0
  224. data/lib/state_machine/integrations/active_model/versions.rb +31 -0
  225. data/lib/state_machine/integrations/active_model.rb +585 -0
  226. data/lib/state_machine/integrations/active_record/locale.rb +20 -0
  227. data/lib/state_machine/integrations/active_record/versions.rb +123 -0
  228. data/lib/state_machine/integrations/active_record.rb +548 -0
  229. data/lib/state_machine/integrations/base.rb +100 -0
  230. data/lib/state_machine/integrations/data_mapper/observer.rb +210 -0
  231. data/lib/state_machine/integrations/data_mapper/versions.rb +85 -0
  232. data/lib/state_machine/integrations/data_mapper.rb +511 -0
  233. data/lib/state_machine/integrations/mongo_mapper/locale.rb +4 -0
  234. data/lib/state_machine/integrations/mongo_mapper/versions.rb +89 -0
  235. data/lib/state_machine/integrations/mongo_mapper.rb +389 -0
  236. data/lib/state_machine/integrations/mongoid/locale.rb +4 -0
  237. data/lib/state_machine/integrations/mongoid/versions.rb +81 -0
  238. data/lib/state_machine/integrations/mongoid.rb +461 -0
  239. data/lib/state_machine/integrations/sequel/versions.rb +95 -0
  240. data/lib/state_machine/integrations/sequel.rb +486 -0
  241. data/lib/state_machine/integrations.rb +121 -0
  242. data/lib/state_machine/machine.rb +2292 -0
  243. data/lib/state_machine/machine_collection.rb +86 -0
  244. data/lib/state_machine/macro_methods.rb +522 -0
  245. data/lib/state_machine/matcher.rb +123 -0
  246. data/lib/state_machine/matcher_helpers.rb +54 -0
  247. data/lib/state_machine/node_collection.rb +222 -0
  248. data/lib/state_machine/path.rb +120 -0
  249. data/lib/state_machine/path_collection.rb +90 -0
  250. data/lib/state_machine/state.rb +297 -0
  251. data/lib/state_machine/state_collection.rb +112 -0
  252. data/lib/state_machine/state_context.rb +138 -0
  253. data/lib/state_machine/transition.rb +470 -0
  254. data/lib/state_machine/transition_collection.rb +245 -0
  255. data/lib/state_machine/version.rb +3 -0
  256. data/lib/state_machine/yard/handlers/base.rb +32 -0
  257. data/lib/state_machine/yard/handlers/event.rb +25 -0
  258. data/lib/state_machine/yard/handlers/machine.rb +344 -0
  259. data/lib/state_machine/yard/handlers/state.rb +25 -0
  260. data/lib/state_machine/yard/handlers/transition.rb +47 -0
  261. data/lib/state_machine/yard/handlers.rb +12 -0
  262. data/lib/state_machine/yard/templates/default/class/html/setup.rb +30 -0
  263. data/lib/state_machine/yard/templates/default/class/html/state_machines.erb +12 -0
  264. data/lib/state_machine/yard/templates.rb +3 -0
  265. data/lib/state_machine/yard.rb +8 -0
  266. data/lib/state_machine.rb +8 -0
  267. data/lib/tasks/state_machine.rake +1 -0
  268. data/lib/tasks/state_machine.rb +30 -0
  269. data/lib/yard-state_machine.rb +2 -0
  270. data/state_machine.gemspec +23 -0
  271. data/test/files/en.yml +17 -0
  272. data/test/files/switch.rb +15 -0
  273. data/test/functional/state_machine_test.rb +1066 -0
  274. data/test/test_helper.rb +7 -0
  275. data/test/unit/assertions_test.rb +40 -0
  276. data/test/unit/branch_test.rb +969 -0
  277. data/test/unit/callback_test.rb +704 -0
  278. data/test/unit/error_test.rb +43 -0
  279. data/test/unit/eval_helpers_test.rb +272 -0
  280. data/test/unit/event_collection_test.rb +398 -0
  281. data/test/unit/event_test.rb +1196 -0
  282. data/test/unit/graph_test.rb +98 -0
  283. data/test/unit/helper_module_test.rb +17 -0
  284. data/test/unit/integrations/active_model_test.rb +1245 -0
  285. data/test/unit/integrations/active_record_test.rb +2551 -0
  286. data/test/unit/integrations/base_test.rb +104 -0
  287. data/test/unit/integrations/data_mapper_test.rb +2194 -0
  288. data/test/unit/integrations/mongo_mapper_test.rb +2026 -0
  289. data/test/unit/integrations/mongoid_test.rb +2309 -0
  290. data/test/unit/integrations/sequel_test.rb +1896 -0
  291. data/test/unit/integrations_test.rb +83 -0
  292. data/test/unit/invalid_event_test.rb +20 -0
  293. data/test/unit/invalid_parallel_transition_test.rb +18 -0
  294. data/test/unit/invalid_transition_test.rb +115 -0
  295. data/test/unit/machine_collection_test.rb +603 -0
  296. data/test/unit/machine_test.rb +3407 -0
  297. data/test/unit/matcher_helpers_test.rb +37 -0
  298. data/test/unit/matcher_test.rb +155 -0
  299. data/test/unit/node_collection_test.rb +362 -0
  300. data/test/unit/path_collection_test.rb +266 -0
  301. data/test/unit/path_test.rb +485 -0
  302. data/test/unit/state_collection_test.rb +352 -0
  303. data/test/unit/state_context_test.rb +441 -0
  304. data/test/unit/state_machine_test.rb +31 -0
  305. data/test/unit/state_test.rb +1101 -0
  306. data/test/unit/transition_collection_test.rb +2168 -0
  307. data/test/unit/transition_test.rb +1558 -0
  308. metadata +450 -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.exist?("./#{@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.exist?(@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.exist?(@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.exist?(@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']