glebtv_state_machine 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (310) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +2 -0
  5. data/.travis.yml +72 -0
  6. data/.yardopts +5 -0
  7. data/Appraisals +491 -0
  8. data/CHANGELOG.md +502 -0
  9. data/Gemfile +3 -0
  10. data/LICENSE +20 -0
  11. data/README.md +1244 -0
  12. data/Rakefile +42 -0
  13. data/examples/AutoShop_state.png +0 -0
  14. data/examples/Car_state.png +0 -0
  15. data/examples/Gemfile +5 -0
  16. data/examples/Gemfile.lock +14 -0
  17. data/examples/TrafficLight_state.png +0 -0
  18. data/examples/Vehicle_state.png +0 -0
  19. data/examples/auto_shop.rb +13 -0
  20. data/examples/car.rb +21 -0
  21. data/examples/doc/AutoShop.html +2856 -0
  22. data/examples/doc/AutoShop_state.png +0 -0
  23. data/examples/doc/Car.html +919 -0
  24. data/examples/doc/Car_state.png +0 -0
  25. data/examples/doc/TrafficLight.html +2230 -0
  26. data/examples/doc/TrafficLight_state.png +0 -0
  27. data/examples/doc/Vehicle.html +7921 -0
  28. data/examples/doc/Vehicle_state.png +0 -0
  29. data/examples/doc/_index.html +136 -0
  30. data/examples/doc/class_list.html +47 -0
  31. data/examples/doc/css/common.css +1 -0
  32. data/examples/doc/css/full_list.css +55 -0
  33. data/examples/doc/css/style.css +322 -0
  34. data/examples/doc/file_list.html +46 -0
  35. data/examples/doc/frames.html +13 -0
  36. data/examples/doc/index.html +136 -0
  37. data/examples/doc/js/app.js +205 -0
  38. data/examples/doc/js/full_list.js +173 -0
  39. data/examples/doc/js/jquery.js +16 -0
  40. data/examples/doc/method_list.html +734 -0
  41. data/examples/doc/top-level-namespace.html +105 -0
  42. data/examples/merb-rest/controller.rb +51 -0
  43. data/examples/merb-rest/model.rb +28 -0
  44. data/examples/merb-rest/view_edit.html.erb +24 -0
  45. data/examples/merb-rest/view_index.html.erb +23 -0
  46. data/examples/merb-rest/view_new.html.erb +13 -0
  47. data/examples/merb-rest/view_show.html.erb +17 -0
  48. data/examples/rails-rest/controller.rb +43 -0
  49. data/examples/rails-rest/migration.rb +7 -0
  50. data/examples/rails-rest/model.rb +23 -0
  51. data/examples/rails-rest/view__form.html.erb +34 -0
  52. data/examples/rails-rest/view_edit.html.erb +6 -0
  53. data/examples/rails-rest/view_index.html.erb +25 -0
  54. data/examples/rails-rest/view_new.html.erb +5 -0
  55. data/examples/rails-rest/view_show.html.erb +19 -0
  56. data/examples/traffic_light.rb +9 -0
  57. data/examples/vehicle.rb +33 -0
  58. data/gemfiles/active_model_3.0.0.gemfile +7 -0
  59. data/gemfiles/active_model_3.0.0.gemfile.lock +35 -0
  60. data/gemfiles/active_model_3.0.5.gemfile +7 -0
  61. data/gemfiles/active_model_3.0.5.gemfile.lock +35 -0
  62. data/gemfiles/active_model_3.1.1.gemfile +7 -0
  63. data/gemfiles/active_model_3.1.1.gemfile.lock +36 -0
  64. data/gemfiles/active_model_3.2.1.gemfile +7 -0
  65. data/gemfiles/active_model_3.2.12.gemfile +7 -0
  66. data/gemfiles/active_model_3.2.12.gemfile.lock +36 -0
  67. data/gemfiles/active_model_3.2.13.rc1.gemfile +7 -0
  68. data/gemfiles/active_model_3.2.13.rc1.gemfile.lock +36 -0
  69. data/gemfiles/active_model_4.0.0.gemfile +9 -0
  70. data/gemfiles/active_model_4.0.0.gemfile.lock +78 -0
  71. data/gemfiles/active_record_2.0.0.gemfile +9 -0
  72. data/gemfiles/active_record_2.0.0.gemfile.lock +39 -0
  73. data/gemfiles/active_record_2.0.5.gemfile +9 -0
  74. data/gemfiles/active_record_2.0.5.gemfile.lock +39 -0
  75. data/gemfiles/active_record_2.1.0.gemfile +9 -0
  76. data/gemfiles/active_record_2.1.0.gemfile.lock +39 -0
  77. data/gemfiles/active_record_2.1.2.gemfile +9 -0
  78. data/gemfiles/active_record_2.1.2.gemfile.lock +39 -0
  79. data/gemfiles/active_record_2.2.3.gemfile +9 -0
  80. data/gemfiles/active_record_2.2.3.gemfile.lock +39 -0
  81. data/gemfiles/active_record_2.3.12.gemfile +9 -0
  82. data/gemfiles/active_record_2.3.12.gemfile.lock +39 -0
  83. data/gemfiles/active_record_2.3.5.gemfile +9 -0
  84. data/gemfiles/active_record_2.3.5.gemfile.lock +39 -0
  85. data/gemfiles/active_record_3.0.0.gemfile +9 -0
  86. data/gemfiles/active_record_3.0.0.gemfile.lock +51 -0
  87. data/gemfiles/active_record_3.0.5.gemfile +9 -0
  88. data/gemfiles/active_record_3.0.5.gemfile.lock +50 -0
  89. data/gemfiles/active_record_3.1.1.gemfile +9 -0
  90. data/gemfiles/active_record_3.1.1.gemfile.lock +51 -0
  91. data/gemfiles/active_record_3.2.12.gemfile +9 -0
  92. data/gemfiles/active_record_3.2.12.gemfile.lock +51 -0
  93. data/gemfiles/active_record_3.2.13.rc1.gemfile +9 -0
  94. data/gemfiles/active_record_3.2.13.rc1.gemfile.lock +51 -0
  95. data/gemfiles/active_record_4.0.0.gemfile +11 -0
  96. data/gemfiles/active_record_4.0.0.gemfile.lock +83 -0
  97. data/gemfiles/data_mapper_0.10.2.gemfile +13 -0
  98. data/gemfiles/data_mapper_0.10.2.gemfile.lock +56 -0
  99. data/gemfiles/data_mapper_0.9.11.gemfile +13 -0
  100. data/gemfiles/data_mapper_0.9.11.gemfile.lock +71 -0
  101. data/gemfiles/data_mapper_0.9.4.gemfile +12 -0
  102. data/gemfiles/data_mapper_0.9.4.gemfile.lock +70 -0
  103. data/gemfiles/data_mapper_0.9.7.gemfile +13 -0
  104. data/gemfiles/data_mapper_0.9.7.gemfile.lock +67 -0
  105. data/gemfiles/data_mapper_1.0.0.gemfile +12 -0
  106. data/gemfiles/data_mapper_1.0.0.gemfile.lock +63 -0
  107. data/gemfiles/data_mapper_1.0.1.gemfile +12 -0
  108. data/gemfiles/data_mapper_1.0.1.gemfile.lock +63 -0
  109. data/gemfiles/data_mapper_1.0.2.gemfile +12 -0
  110. data/gemfiles/data_mapper_1.0.2.gemfile.lock +63 -0
  111. data/gemfiles/data_mapper_1.1.0.gemfile +12 -0
  112. data/gemfiles/data_mapper_1.1.0.gemfile.lock +61 -0
  113. data/gemfiles/data_mapper_1.2.0.gemfile +12 -0
  114. data/gemfiles/data_mapper_1.2.0.gemfile.lock +61 -0
  115. data/gemfiles/default.gemfile +7 -0
  116. data/gemfiles/default.gemfile.lock +27 -0
  117. data/gemfiles/graphviz_0.9.17.gemfile +7 -0
  118. data/gemfiles/graphviz_0.9.17.gemfile.lock +29 -0
  119. data/gemfiles/graphviz_0.9.21.gemfile +7 -0
  120. data/gemfiles/graphviz_0.9.21.gemfile.lock +29 -0
  121. data/gemfiles/graphviz_1.0.0.gemfile +7 -0
  122. data/gemfiles/graphviz_1.0.0.gemfile.lock +29 -0
  123. data/gemfiles/graphviz_1.0.3.gemfile +7 -0
  124. data/gemfiles/graphviz_1.0.3.gemfile.lock +29 -0
  125. data/gemfiles/graphviz_1.0.8.gemfile +7 -0
  126. data/gemfiles/graphviz_1.0.8.gemfile.lock +29 -0
  127. data/gemfiles/mongo_mapper_0.10.0.gemfile +8 -0
  128. data/gemfiles/mongo_mapper_0.10.0.gemfile.lock +47 -0
  129. data/gemfiles/mongo_mapper_0.11.2.gemfile +9 -0
  130. data/gemfiles/mongo_mapper_0.11.2.gemfile.lock +48 -0
  131. data/gemfiles/mongo_mapper_0.12.0.gemfile +9 -0
  132. data/gemfiles/mongo_mapper_0.12.0.gemfile.lock +48 -0
  133. data/gemfiles/mongo_mapper_0.5.5.gemfile +8 -0
  134. data/gemfiles/mongo_mapper_0.5.5.gemfile.lock +36 -0
  135. data/gemfiles/mongo_mapper_0.5.8.gemfile +8 -0
  136. data/gemfiles/mongo_mapper_0.5.8.gemfile.lock +36 -0
  137. data/gemfiles/mongo_mapper_0.6.0.gemfile +8 -0
  138. data/gemfiles/mongo_mapper_0.6.0.gemfile.lock +36 -0
  139. data/gemfiles/mongo_mapper_0.6.10.gemfile +8 -0
  140. data/gemfiles/mongo_mapper_0.6.10.gemfile.lock +36 -0
  141. data/gemfiles/mongo_mapper_0.7.0.gemfile +8 -0
  142. data/gemfiles/mongo_mapper_0.7.0.gemfile.lock +36 -0
  143. data/gemfiles/mongo_mapper_0.7.5.gemfile +8 -0
  144. data/gemfiles/mongo_mapper_0.7.5.gemfile.lock +39 -0
  145. data/gemfiles/mongo_mapper_0.8.0.gemfile +10 -0
  146. data/gemfiles/mongo_mapper_0.8.0.gemfile.lock +43 -0
  147. data/gemfiles/mongo_mapper_0.8.3.gemfile +10 -0
  148. data/gemfiles/mongo_mapper_0.8.3.gemfile.lock +43 -0
  149. data/gemfiles/mongo_mapper_0.8.4.gemfile +8 -0
  150. data/gemfiles/mongo_mapper_0.8.4.gemfile.lock +42 -0
  151. data/gemfiles/mongo_mapper_0.8.6.gemfile +8 -0
  152. data/gemfiles/mongo_mapper_0.8.6.gemfile.lock +42 -0
  153. data/gemfiles/mongo_mapper_0.9.0.gemfile +7 -0
  154. data/gemfiles/mongo_mapper_0.9.0.gemfile.lock +45 -0
  155. data/gemfiles/mongoid_2.0.0.gemfile +9 -0
  156. data/gemfiles/mongoid_2.0.0.gemfile.lock +49 -0
  157. data/gemfiles/mongoid_2.1.4.gemfile +9 -0
  158. data/gemfiles/mongoid_2.1.4.gemfile.lock +47 -0
  159. data/gemfiles/mongoid_2.2.4.gemfile +9 -0
  160. data/gemfiles/mongoid_2.2.4.gemfile.lock +47 -0
  161. data/gemfiles/mongoid_2.3.3.gemfile +9 -0
  162. data/gemfiles/mongoid_2.3.3.gemfile.lock +47 -0
  163. data/gemfiles/mongoid_2.4.0.gemfile +9 -0
  164. data/gemfiles/mongoid_2.4.0.gemfile.lock +47 -0
  165. data/gemfiles/mongoid_2.4.10.gemfile +9 -0
  166. data/gemfiles/mongoid_2.4.10.gemfile.lock +47 -0
  167. data/gemfiles/mongoid_2.5.2.gemfile +9 -0
  168. data/gemfiles/mongoid_2.5.2.gemfile.lock +47 -0
  169. data/gemfiles/mongoid_2.6.0.gemfile +9 -0
  170. data/gemfiles/mongoid_2.6.0.gemfile.lock +47 -0
  171. data/gemfiles/mongoid_3.0.0.gemfile +8 -0
  172. data/gemfiles/mongoid_3.0.0.gemfile.lock +45 -0
  173. data/gemfiles/mongoid_3.0.22.gemfile +8 -0
  174. data/gemfiles/mongoid_3.0.22.gemfile.lock +45 -0
  175. data/gemfiles/mongoid_3.1.0.gemfile +8 -0
  176. data/gemfiles/mongoid_3.1.0.gemfile.lock +45 -0
  177. data/gemfiles/sequel_2.11.0.gemfile +9 -0
  178. data/gemfiles/sequel_2.11.0.gemfile.lock +33 -0
  179. data/gemfiles/sequel_2.12.0.gemfile +9 -0
  180. data/gemfiles/sequel_2.12.0.gemfile.lock +33 -0
  181. data/gemfiles/sequel_2.8.0.gemfile +9 -0
  182. data/gemfiles/sequel_2.8.0.gemfile.lock +33 -0
  183. data/gemfiles/sequel_3.0.0.gemfile +9 -0
  184. data/gemfiles/sequel_3.0.0.gemfile.lock +33 -0
  185. data/gemfiles/sequel_3.10.0.gemfile +9 -0
  186. data/gemfiles/sequel_3.10.0.gemfile.lock +33 -0
  187. data/gemfiles/sequel_3.13.0.gemfile +9 -0
  188. data/gemfiles/sequel_3.13.0.gemfile.lock +33 -0
  189. data/gemfiles/sequel_3.14.0.gemfile +9 -0
  190. data/gemfiles/sequel_3.14.0.gemfile.lock +33 -0
  191. data/gemfiles/sequel_3.23.0.gemfile +9 -0
  192. data/gemfiles/sequel_3.23.0.gemfile.lock +33 -0
  193. data/gemfiles/sequel_3.24.0.gemfile +9 -0
  194. data/gemfiles/sequel_3.24.0.gemfile.lock +33 -0
  195. data/gemfiles/sequel_3.29.0.gemfile +9 -0
  196. data/gemfiles/sequel_3.29.0.gemfile.lock +33 -0
  197. data/gemfiles/sequel_3.34.0.gemfile +9 -0
  198. data/gemfiles/sequel_3.34.0.gemfile.lock +33 -0
  199. data/gemfiles/sequel_3.35.0.gemfile +9 -0
  200. data/gemfiles/sequel_3.35.0.gemfile.lock +33 -0
  201. data/gemfiles/sequel_3.4.0.gemfile +9 -0
  202. data/gemfiles/sequel_3.4.0.gemfile.lock +33 -0
  203. data/gemfiles/sequel_3.44.0.gemfile +9 -0
  204. data/gemfiles/sequel_3.44.0.gemfile.lock +33 -0
  205. data/glebtv_state_machine.gemspec +22 -0
  206. data/init.rb +1 -0
  207. data/lib/glebtv_state_machine.rb +1 -0
  208. data/lib/state_machine.rb +8 -0
  209. data/lib/state_machine/assertions.rb +36 -0
  210. data/lib/state_machine/branch.rb +225 -0
  211. data/lib/state_machine/callback.rb +236 -0
  212. data/lib/state_machine/core.rb +12 -0
  213. data/lib/state_machine/core_ext.rb +2 -0
  214. data/lib/state_machine/core_ext/class/state_machine.rb +5 -0
  215. data/lib/state_machine/error.rb +13 -0
  216. data/lib/state_machine/eval_helpers.rb +87 -0
  217. data/lib/state_machine/event.rb +257 -0
  218. data/lib/state_machine/event_collection.rb +141 -0
  219. data/lib/state_machine/extensions.rb +149 -0
  220. data/lib/state_machine/graph.rb +92 -0
  221. data/lib/state_machine/helper_module.rb +17 -0
  222. data/lib/state_machine/initializers.rb +4 -0
  223. data/lib/state_machine/initializers/merb.rb +1 -0
  224. data/lib/state_machine/initializers/rails.rb +25 -0
  225. data/lib/state_machine/integrations.rb +121 -0
  226. data/lib/state_machine/integrations/active_model.rb +585 -0
  227. data/lib/state_machine/integrations/active_model/locale.rb +11 -0
  228. data/lib/state_machine/integrations/active_model/observer.rb +33 -0
  229. data/lib/state_machine/integrations/active_model/observer_update.rb +42 -0
  230. data/lib/state_machine/integrations/active_model/versions.rb +31 -0
  231. data/lib/state_machine/integrations/active_record.rb +548 -0
  232. data/lib/state_machine/integrations/active_record/locale.rb +20 -0
  233. data/lib/state_machine/integrations/active_record/versions.rb +123 -0
  234. data/lib/state_machine/integrations/base.rb +100 -0
  235. data/lib/state_machine/integrations/data_mapper.rb +511 -0
  236. data/lib/state_machine/integrations/data_mapper/observer.rb +210 -0
  237. data/lib/state_machine/integrations/data_mapper/versions.rb +85 -0
  238. data/lib/state_machine/integrations/mongo_mapper.rb +389 -0
  239. data/lib/state_machine/integrations/mongo_mapper/locale.rb +4 -0
  240. data/lib/state_machine/integrations/mongo_mapper/versions.rb +89 -0
  241. data/lib/state_machine/integrations/mongoid.rb +465 -0
  242. data/lib/state_machine/integrations/mongoid/locale.rb +4 -0
  243. data/lib/state_machine/integrations/mongoid/versions.rb +81 -0
  244. data/lib/state_machine/integrations/sequel.rb +486 -0
  245. data/lib/state_machine/integrations/sequel/versions.rb +95 -0
  246. data/lib/state_machine/machine.rb +2292 -0
  247. data/lib/state_machine/machine_collection.rb +86 -0
  248. data/lib/state_machine/macro_methods.rb +522 -0
  249. data/lib/state_machine/matcher.rb +123 -0
  250. data/lib/state_machine/matcher_helpers.rb +54 -0
  251. data/lib/state_machine/node_collection.rb +222 -0
  252. data/lib/state_machine/path.rb +120 -0
  253. data/lib/state_machine/path_collection.rb +90 -0
  254. data/lib/state_machine/state.rb +297 -0
  255. data/lib/state_machine/state_collection.rb +112 -0
  256. data/lib/state_machine/state_context.rb +138 -0
  257. data/lib/state_machine/transition.rb +470 -0
  258. data/lib/state_machine/transition_collection.rb +245 -0
  259. data/lib/state_machine/version.rb +3 -0
  260. data/lib/state_machine/yard.rb +8 -0
  261. data/lib/state_machine/yard/handlers.rb +12 -0
  262. data/lib/state_machine/yard/handlers/base.rb +32 -0
  263. data/lib/state_machine/yard/handlers/event.rb +25 -0
  264. data/lib/state_machine/yard/handlers/machine.rb +344 -0
  265. data/lib/state_machine/yard/handlers/state.rb +25 -0
  266. data/lib/state_machine/yard/handlers/transition.rb +47 -0
  267. data/lib/state_machine/yard/templates.rb +3 -0
  268. data/lib/state_machine/yard/templates/default/class/html/setup.rb +30 -0
  269. data/lib/state_machine/yard/templates/default/class/html/state_machines.erb +12 -0
  270. data/lib/tasks/state_machine.rake +1 -0
  271. data/lib/tasks/state_machine.rb +30 -0
  272. data/lib/yard-state_machine.rb +2 -0
  273. data/test/files/en.yml +17 -0
  274. data/test/files/switch.rb +15 -0
  275. data/test/functional/state_machine_test.rb +1066 -0
  276. data/test/test_helper.rb +7 -0
  277. data/test/unit/assertions_test.rb +40 -0
  278. data/test/unit/branch_test.rb +969 -0
  279. data/test/unit/callback_test.rb +704 -0
  280. data/test/unit/error_test.rb +43 -0
  281. data/test/unit/eval_helpers_test.rb +270 -0
  282. data/test/unit/event_collection_test.rb +398 -0
  283. data/test/unit/event_test.rb +1196 -0
  284. data/test/unit/graph_test.rb +98 -0
  285. data/test/unit/helper_module_test.rb +17 -0
  286. data/test/unit/integrations/active_model_test.rb +1245 -0
  287. data/test/unit/integrations/active_record_test.rb +2551 -0
  288. data/test/unit/integrations/base_test.rb +104 -0
  289. data/test/unit/integrations/data_mapper_test.rb +2194 -0
  290. data/test/unit/integrations/mongo_mapper_test.rb +2026 -0
  291. data/test/unit/integrations/mongoid_test.rb +2309 -0
  292. data/test/unit/integrations/sequel_test.rb +1896 -0
  293. data/test/unit/integrations_test.rb +83 -0
  294. data/test/unit/invalid_event_test.rb +20 -0
  295. data/test/unit/invalid_parallel_transition_test.rb +18 -0
  296. data/test/unit/invalid_transition_test.rb +115 -0
  297. data/test/unit/machine_collection_test.rb +603 -0
  298. data/test/unit/machine_test.rb +3407 -0
  299. data/test/unit/matcher_helpers_test.rb +37 -0
  300. data/test/unit/matcher_test.rb +155 -0
  301. data/test/unit/node_collection_test.rb +362 -0
  302. data/test/unit/path_collection_test.rb +266 -0
  303. data/test/unit/path_test.rb +485 -0
  304. data/test/unit/state_collection_test.rb +352 -0
  305. data/test/unit/state_context_test.rb +441 -0
  306. data/test/unit/state_machine_test.rb +31 -0
  307. data/test/unit/state_test.rb +1101 -0
  308. data/test/unit/transition_collection_test.rb +2168 -0
  309. data/test/unit/transition_test.rb +1558 -0
  310. metadata +438 -0
@@ -0,0 +1,2026 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
2
+
3
+ require 'mongo_mapper'
4
+
5
+ # Establish database connection
6
+ MongoMapper.connection = Mongo::Connection.new('127.0.0.1', 27017, {:logger => Logger.new("#{File.dirname(__FILE__)}/../../mongo_mapper.log"), :slave_ok => true})
7
+ MongoMapper.database = 'test'
8
+
9
+ module MongoMapperTest
10
+ class BaseTestCase < Test::Unit::TestCase
11
+ def default_test
12
+ end
13
+
14
+ def teardown
15
+ if @table_names
16
+ MongoMapper.database.collections.each {|c| c.drop if @table_names.include?(c.name)}
17
+ end
18
+ end
19
+
20
+ protected
21
+ # Creates a new MongoMapper model (and the associated table)
22
+ def new_model(name = :foo, &block)
23
+ table_name = "#{name}_#{rand(1000000)}"
24
+ @table_names ||= []
25
+ @table_names << table_name
26
+
27
+ model = Class.new do
28
+ (class << self; self; end).class_eval do
29
+ define_method(:name) { "MongoMapperTest::#{name.to_s.capitalize}" }
30
+ define_method(:to_s) { self.name }
31
+ end
32
+ end
33
+
34
+ model.class_eval do
35
+ include MongoMapper::Document
36
+ set_collection_name(table_name)
37
+
38
+ key :state, String
39
+ end
40
+ model.class_eval(&block) if block_given?
41
+ model
42
+ end
43
+ end
44
+
45
+ class IntegrationTest < BaseTestCase
46
+ def test_should_have_an_integration_name
47
+ assert_equal :mongo_mapper, StateMachine::Integrations::MongoMapper.integration_name
48
+ end
49
+
50
+ def test_should_be_available
51
+ assert StateMachine::Integrations::MongoMapper.available?
52
+ end
53
+
54
+ def test_should_match_if_class_includes_mongo_mapper
55
+ assert StateMachine::Integrations::MongoMapper.matches?(new_model)
56
+ end
57
+
58
+ def test_should_not_match_if_class_does_not_include_mongo_mapper
59
+ assert !StateMachine::Integrations::MongoMapper.matches?(Class.new)
60
+ end
61
+
62
+ def test_should_have_defaults
63
+ assert_equal({:action => :save}, StateMachine::Integrations::MongoMapper.defaults)
64
+ end
65
+
66
+ def test_should_have_a_locale_path
67
+ assert_not_nil StateMachine::Integrations::MongoMapper.locale_path
68
+ end
69
+ end
70
+
71
+ class MachineWithoutDatabaseTest < BaseTestCase
72
+ def setup
73
+ @model = new_model do
74
+ # Simulate the database not being available entirely
75
+ def self.connection
76
+ raise Mongo::ConnectionFailure
77
+ end
78
+ end
79
+ end
80
+
81
+ def test_should_allow_machine_creation
82
+ assert_nothing_raised { StateMachine::Machine.new(@model) }
83
+ end
84
+ end
85
+
86
+ class MachineWithoutKeyTest < BaseTestCase
87
+ def setup
88
+ @model = new_model
89
+ StateMachine::Machine.new(@model, :status)
90
+ end
91
+
92
+ def test_should_define_field_with_string_type
93
+ key = @model.keys['status']
94
+ assert_not_nil key
95
+ assert_equal String, key.type
96
+ end
97
+ end
98
+
99
+ class MachineWithKeyTest < BaseTestCase
100
+ def setup
101
+ @model = new_model do
102
+ key :status, Integer
103
+ end
104
+ StateMachine::Machine.new(@model, :status)
105
+ end
106
+
107
+ def test_should_not_redefine_field
108
+ key = @model.keys['status']
109
+ assert_not_nil key
110
+ assert_equal Integer, key.type
111
+ end
112
+ end
113
+
114
+ class MachineByDefaultTest < BaseTestCase
115
+ def setup
116
+ @model = new_model
117
+ @machine = StateMachine::Machine.new(@model)
118
+ end
119
+
120
+ def test_should_use_save_as_action
121
+ assert_equal :save, @machine.action
122
+ end
123
+
124
+ def test_should_not_have_any_before_callbacks
125
+ assert_equal 0, @machine.callbacks[:before].size
126
+ end
127
+
128
+ def test_should_not_have_any_after_callbacks
129
+ assert_equal 0, @machine.callbacks[:after].size
130
+ end
131
+ end
132
+
133
+ class MachineWithStatesTest < BaseTestCase
134
+ def setup
135
+ @model = new_model
136
+ @machine = StateMachine::Machine.new(@model)
137
+ @machine.state :first_gear
138
+ end
139
+
140
+ def test_should_humanize_name
141
+ assert_equal 'first gear', @machine.state(:first_gear).human_name
142
+ end
143
+ end
144
+
145
+ class MachineWithStaticInitialStateTest < BaseTestCase
146
+ def setup
147
+ @model = new_model(:vehicle) do
148
+ attr_accessor :value
149
+ end
150
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
151
+ end
152
+
153
+ def test_should_set_initial_state_on_created_object
154
+ record = @model.new
155
+ assert_equal 'parked', record.state
156
+ end
157
+
158
+ def test_should_set_initial_state_with_nil_attributes
159
+ record = @model.new(nil)
160
+ assert_equal 'parked', record.state
161
+ end
162
+
163
+ def test_should_still_set_attributes
164
+ record = @model.new(:value => 1)
165
+ assert_equal 1, record.value
166
+ end
167
+
168
+ def test_should_not_allow_initialize_blocks
169
+ block_args = nil
170
+ @model.new do |*args|
171
+ block_args = args
172
+ end
173
+
174
+ assert_nil block_args
175
+ end
176
+
177
+ def test_should_set_initial_state_before_setting_attributes
178
+ @model.class_eval do
179
+ attr_accessor :state_during_setter
180
+
181
+ remove_method :value=
182
+ define_method(:value=) do |value|
183
+ self.state_during_setter = state
184
+ end
185
+ end
186
+
187
+ record = @model.new(:value => 1)
188
+ assert_equal 'parked', record.state_during_setter
189
+ end
190
+
191
+ def test_should_not_set_initial_state_after_already_initialized
192
+ record = @model.new(:value => 1)
193
+ assert_equal 'parked', record.state
194
+
195
+ record.state = 'idling'
196
+ record.attributes = {}
197
+ assert_equal 'idling', record.state
198
+ end
199
+
200
+ def test_should_persist_initial_state
201
+ record = @model.new
202
+ record.save
203
+ record = @model.find(record.id)
204
+ assert_equal 'parked', record.state
205
+ end
206
+
207
+ def test_should_persist_initial_state_on_dup
208
+ record = @model.create.dup
209
+ record.save
210
+ record = @model.find(record.id)
211
+ assert_equal 'parked', record.state
212
+ end
213
+
214
+ def test_should_use_stored_values_when_loading_from_database
215
+ @machine.state :idling
216
+
217
+ record = @model.find(@model.create(:state => 'idling').id)
218
+ assert_equal 'idling', record.state
219
+ end
220
+
221
+ def test_should_use_stored_values_when_loading_from_database_with_nil_state
222
+ @machine.state nil
223
+
224
+ record = @model.find(@model.create(:state => nil).id)
225
+ assert_nil record.state
226
+ end
227
+
228
+ if defined?(MongoMapper::Version) && MongoMapper::Version !~ /^0\.[5-8]\./
229
+ def test_should_use_stored_values_when_loading_for_many_association
230
+ @machine.state :idling
231
+
232
+ @model.belongs_to :owner, :class_name => 'MongoMapperTest::Owner'
233
+ MongoMapperTest.const_set('Vehicle', @model)
234
+
235
+ owner_model = new_model(:owner) do
236
+ many :vehicles, :class_name => 'MongoMapperTest::Vehicle'
237
+ end
238
+ MongoMapperTest.const_set('Owner', owner_model)
239
+
240
+ owner = owner_model.create
241
+ record = @model.create(:state => 'idling', :owner_id => owner.id)
242
+ assert_equal 'idling', owner.vehicles[0].state
243
+ end
244
+
245
+ def test_should_use_stored_values_when_loading_for_one_association
246
+ @machine.state :idling
247
+
248
+ @model.belongs_to :owner, :class_name => 'MongoMapperTest::Owner'
249
+ MongoMapperTest.const_set('Vehicle', @model)
250
+
251
+ owner_model = new_model(:owner) do
252
+ one :vehicle, :class_name => 'MongoMapperTest::Vehicle'
253
+ end
254
+ MongoMapperTest.const_set('Owner', owner_model)
255
+
256
+ owner = owner_model.create
257
+ record = @model.create(:state => 'idling', :owner_id => owner.id)
258
+ owner.reload
259
+ assert_equal 'idling', owner.vehicle.state
260
+ end
261
+
262
+ def test_should_use_stored_values_when_loading_for_belongs_to_association
263
+ @machine.state :idling
264
+
265
+ MongoMapperTest.const_set('Vehicle', @model)
266
+
267
+ driver_model = new_model(:driver) do
268
+ belongs_to :vehicle, :class_name => 'MongoMapperTest::Vehicle'
269
+ end
270
+ MongoMapperTest.const_set('Driver', driver_model)
271
+
272
+ record = @model.create(:state => 'idling')
273
+ driver = driver_model.create(:vehicle_id => record.id)
274
+ assert_equal 'idling', driver.vehicle.state
275
+ end
276
+ end
277
+
278
+ def teardown
279
+ MongoMapperTest.class_eval do
280
+ remove_const('Vehicle') if defined?(MongoMapperTest::Vehicle)
281
+ remove_const('Owner') if defined?(MongoMapperTest::Owner)
282
+ remove_const('Driver') if defined?(MongoMapperTest::Driver)
283
+ end
284
+ super
285
+ end
286
+ end
287
+
288
+ class MachineWithDynamicInitialStateTest < BaseTestCase
289
+ def setup
290
+ @model = new_model do
291
+ attr_accessor :value
292
+ end
293
+ @machine = StateMachine::Machine.new(@model, :initial => lambda {|object| :parked})
294
+ @machine.state :parked
295
+ end
296
+
297
+ def test_should_set_initial_state_on_created_object
298
+ record = @model.new
299
+ assert_equal 'parked', record.state
300
+ end
301
+
302
+ def test_should_still_set_attributes
303
+ record = @model.new(:value => 1)
304
+ assert_equal 1, record.value
305
+ end
306
+
307
+ def test_should_not_allow_initialize_blocks
308
+ block_args = nil
309
+ @model.new do |*args|
310
+ block_args = args
311
+ end
312
+
313
+ assert_nil block_args
314
+ end
315
+
316
+ def test_should_set_initial_state_after_setting_attributes
317
+ @model.class_eval do
318
+ attr_accessor :state_during_setter
319
+
320
+ remove_method :value=
321
+ define_method(:value=) do |value|
322
+ self.state_during_setter = state || 'nil'
323
+ end
324
+ end
325
+
326
+ record = @model.new(:value => 1)
327
+ assert_equal 'nil', record.state_during_setter
328
+ end
329
+
330
+ def test_should_not_set_initial_state_after_already_initialized
331
+ record = @model.new(:value => 1)
332
+ assert_equal 'parked', record.state
333
+
334
+ record.state = 'idling'
335
+ record.attributes = {}
336
+ assert_equal 'idling', record.state
337
+ end
338
+
339
+ def test_should_persist_initial_state
340
+ record = @model.new
341
+ record.save
342
+ record = @model.find(record.id)
343
+ assert_equal 'parked', record.state
344
+ end
345
+
346
+ def test_should_persist_initial_state_on_dup
347
+ record = @model.create.dup
348
+ record.save
349
+ record = @model.find(record.id)
350
+ assert_equal 'parked', record.state
351
+ end
352
+
353
+ def test_should_use_stored_values_when_loading_from_database
354
+ @machine.state :idling
355
+
356
+ record = @model.find(@model.create(:state => 'idling').id)
357
+ assert_equal 'idling', record.state
358
+ end
359
+
360
+ def test_should_use_stored_values_when_loading_from_database_with_nil_state
361
+ @machine.state nil
362
+
363
+ record = @model.find(@model.create(:state => nil).id)
364
+ assert_nil record.state
365
+ end
366
+ end
367
+
368
+ class MachineWithEventsTest < BaseTestCase
369
+ def setup
370
+ @model = new_model
371
+ @machine = StateMachine::Machine.new(@model)
372
+ @machine.event :shift_up
373
+ end
374
+
375
+ def test_should_humanize_name
376
+ assert_equal 'shift up', @machine.event(:shift_up).human_name
377
+ end
378
+ end
379
+
380
+ class MachineWithSameColumnDefaultTest < BaseTestCase
381
+ def setup
382
+ @original_stderr, $stderr = $stderr, StringIO.new
383
+
384
+ @model = new_model do
385
+ key :status, String, :default => 'parked'
386
+ end
387
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
388
+ @record = @model.new
389
+ end
390
+
391
+ def test_should_use_machine_default
392
+ assert_equal 'parked', @record.status
393
+ end
394
+
395
+ def test_should_not_generate_a_warning
396
+ assert_no_match(/have defined a different default/, $stderr.string)
397
+ end
398
+
399
+ def teardown
400
+ $stderr = @original_stderr
401
+ end
402
+ end
403
+
404
+ class MachineWithDifferentColumnDefaultTest < BaseTestCase
405
+ def setup
406
+ @original_stderr, $stderr = $stderr, StringIO.new
407
+
408
+ @model = new_model do
409
+ key :status, String, :default => 'idling'
410
+ end
411
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
412
+ @record = @model.new
413
+ end
414
+
415
+ def test_should_use_machine_default
416
+ assert_equal 'parked', @record.status
417
+ end
418
+
419
+ def test_should_generate_a_warning
420
+ assert_match(/Both MongoMapperTest::Foo and its :status machine have defined a different default for "status". Use only one or the other for defining defaults to avoid unexpected behaviors\./, $stderr.string)
421
+ end
422
+
423
+ def teardown
424
+ $stderr = @original_stderr
425
+ end
426
+ end
427
+
428
+ class MachineWithDifferentIntegerColumnDefaultTest < BaseTestCase
429
+ def setup
430
+ @original_stderr, $stderr = $stderr, StringIO.new
431
+
432
+ @model = new_model do
433
+ key :status, Integer, :default => 0
434
+ end
435
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
436
+ @machine.state :parked, :value => 1
437
+ @record = @model.new
438
+ end
439
+
440
+ def test_should_use_machine_default
441
+ assert_equal 1, @record.status
442
+ end
443
+
444
+ def test_should_generate_a_warning
445
+ assert_match(/Both MongoMapperTest::Foo and its :status machine have defined a different default for "status". Use only one or the other for defining defaults to avoid unexpected behaviors\./, $stderr.string)
446
+ end
447
+
448
+ def teardown
449
+ $stderr = @original_stderr
450
+ end
451
+ end
452
+
453
+ class MachineWithConflictingPredicateTest < BaseTestCase
454
+ def setup
455
+ @model = new_model do
456
+ def state?(*args)
457
+ true
458
+ end
459
+ end
460
+
461
+ @machine = StateMachine::Machine.new(@model)
462
+ @record = @model.new
463
+ end
464
+
465
+ def test_should_not_define_attribute_predicate
466
+ assert @record.state?
467
+ end
468
+ end
469
+
470
+ class MachineWithConflictingStateNameTest < BaseTestCase
471
+ def setup
472
+ require 'stringio'
473
+ @original_stderr, $stderr = $stderr, StringIO.new
474
+
475
+ @model = new_model
476
+ end
477
+
478
+ def test_should_output_warning_with_same_machine_name
479
+ @machine = StateMachine::Machine.new(@model)
480
+ @machine.state :state
481
+
482
+ assert_match(/^Instance method "state\?" is already defined in .*, use generic helper instead.*\n$/, $stderr.string)
483
+ end
484
+
485
+ def test_should_output_warning_with_same_machine_attribute
486
+ @machine = StateMachine::Machine.new(@model, :public_state, :attribute => :state)
487
+ @machine.state :state
488
+
489
+ assert_match(/^Instance method "state\?" is already defined in .*, use generic helper instead.*\n$/, $stderr.string)
490
+ end
491
+
492
+ def teardown
493
+ $stderr = @original_stderr
494
+ end
495
+ end
496
+
497
+ class MachineWithColumnStateAttributeTest < BaseTestCase
498
+ def setup
499
+ @model = new_model
500
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
501
+ @machine.other_states(:idling)
502
+
503
+ @record = @model.new
504
+ end
505
+
506
+ def test_should_not_override_the_column_reader
507
+ @record[:state] = 'parked'
508
+ assert_equal 'parked', @record.state
509
+ end
510
+
511
+ def test_should_not_override_the_column_writer
512
+ @record.state = 'parked'
513
+ assert_equal 'parked', @record[:state]
514
+ end
515
+
516
+ def test_should_have_an_attribute_predicate
517
+ assert @record.respond_to?(:state?)
518
+ end
519
+
520
+ def test_should_test_for_existence_on_predicate_without_parameters
521
+ assert @record.state?
522
+
523
+ @record.state = nil
524
+ assert !@record.state?
525
+ end
526
+
527
+ def test_should_return_false_for_predicate_if_does_not_match_current_value
528
+ assert !@record.state?(:idling)
529
+ end
530
+
531
+ def test_should_return_true_for_predicate_if_matches_current_value
532
+ assert @record.state?(:parked)
533
+ end
534
+
535
+ def test_should_raise_exception_for_predicate_if_invalid_state_specified
536
+ assert_raise(IndexError) { @record.state?(:invalid) }
537
+ end
538
+ end
539
+
540
+ class MachineWithNonColumnStateAttributeUndefinedTest < BaseTestCase
541
+ def setup
542
+ @model = new_model do
543
+ def initialize
544
+ # Skip attribute initialization
545
+ @initialized_state_machines = true
546
+ super
547
+ end
548
+ end
549
+
550
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
551
+ @machine.other_states(:idling)
552
+ @record = @model.new
553
+ end
554
+
555
+ def test_should_define_a_new_key_for_the_attribute
556
+ assert_not_nil @model.keys['status']
557
+ end
558
+
559
+ def test_should_define_a_reader_attribute_for_the_attribute
560
+ assert @record.respond_to?(:status)
561
+ end
562
+
563
+ def test_should_define_a_writer_attribute_for_the_attribute
564
+ assert @record.respond_to?(:status=)
565
+ end
566
+
567
+ def test_should_define_an_attribute_predicate
568
+ assert @record.respond_to?(:status?)
569
+ end
570
+ end
571
+
572
+ class MachineWithNonColumnStateAttributeDefinedTest < BaseTestCase
573
+ def setup
574
+ @model = new_model do
575
+ attr_accessor :status
576
+ end
577
+
578
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
579
+ @machine.other_states(:idling)
580
+ @record = @model.new
581
+ end
582
+
583
+ def test_should_return_false_for_predicate_if_does_not_match_current_value
584
+ assert !@record.status?(:idling)
585
+ end
586
+
587
+ def test_should_return_true_for_predicate_if_matches_current_value
588
+ assert @record.status?(:parked)
589
+ end
590
+
591
+ def test_should_raise_exception_for_predicate_if_invalid_state_specified
592
+ assert_raise(IndexError) { @record.status?(:invalid) }
593
+ end
594
+
595
+ def test_should_set_initial_state_on_created_object
596
+ assert_equal 'parked', @record.status
597
+ end
598
+ end
599
+
600
+ if !defined?(MongoMapper::Version) || MongoMapper::Version =~ /^0\.[5-8]\./
601
+ class MachineWithAliasedAttributeTest < BaseTestCase
602
+ def setup
603
+ @model = new_model do
604
+ alias_attribute :vehicle_status, :state
605
+ end
606
+
607
+ @machine = StateMachine::Machine.new(@model, :status, :attribute => :vehicle_status)
608
+ @machine.state :parked
609
+
610
+ @record = @model.new
611
+ end
612
+
613
+ def test_should_check_custom_attribute_for_predicate
614
+ @record.vehicle_status = nil
615
+ assert !@record.status?(:parked)
616
+
617
+ @record.vehicle_status = 'parked'
618
+ assert @record.status?(:parked)
619
+ end
620
+ end
621
+ end
622
+
623
+ class MachineWithCustomAttributeTest < BaseTestCase
624
+ def setup
625
+ require 'stringio'
626
+ @original_stderr, $stderr = $stderr, StringIO.new
627
+
628
+ @model = new_model
629
+ @machine = StateMachine::Machine.new(@model, :public_state, :attribute => :state)
630
+ @record = @model.new
631
+ end
632
+
633
+ def test_should_not_delegate_attribute_predicate_with_different_attribute
634
+ assert_raise(ArgumentError) { @record.public_state? }
635
+ end
636
+
637
+ def teardown
638
+ $stderr = @original_stderr
639
+ end
640
+ end
641
+
642
+ class MachineWithInitializedStateTest < BaseTestCase
643
+ def setup
644
+ @model = new_model
645
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
646
+ @machine.state :idling
647
+ end
648
+
649
+ def test_should_allow_nil_initial_state_when_static
650
+ @machine.state nil
651
+
652
+ record = @model.new(:state => nil)
653
+ assert_nil record.state
654
+ end
655
+
656
+ def test_should_allow_nil_initial_state_when_dynamic
657
+ @machine.state nil
658
+
659
+ @machine.initial_state = lambda {:parked}
660
+ record = @model.new(:state => nil)
661
+ assert_nil record.state
662
+ end
663
+
664
+ def test_should_allow_different_initial_state_when_static
665
+ record = @model.new(:state => 'idling')
666
+ assert_equal 'idling', record.state
667
+ end
668
+
669
+ def test_should_allow_different_initial_state_when_dynamic
670
+ @machine.initial_state = lambda {:parked}
671
+ record = @model.new(:state => 'idling')
672
+ assert_equal 'idling', record.state
673
+ end
674
+
675
+ if defined?(MongoMapper::Plugins::Protected)
676
+ def test_should_use_default_state_if_protected
677
+ @model.class_eval do
678
+ attr_protected :state
679
+ end
680
+
681
+ record = @model.new(:state => 'idling')
682
+ assert_equal 'parked', record.state
683
+ end
684
+ end
685
+ end
686
+
687
+ class MachineMultipleTest < BaseTestCase
688
+ def setup
689
+ @model = new_model do
690
+ key :status, String
691
+ end
692
+ @state_machine = StateMachine::Machine.new(@model, :initial => :parked)
693
+ @status_machine = StateMachine::Machine.new(@model, :status, :initial => :idling)
694
+ end
695
+
696
+ def test_should_should_initialize_each_state
697
+ record = @model.new
698
+ assert_equal 'parked', record.state
699
+ assert_equal 'idling', record.status
700
+ end
701
+ end
702
+
703
+ class MachineWithLoopbackTest < BaseTestCase
704
+ def setup
705
+ @model = new_model do
706
+ key :updated_at, Time
707
+
708
+ before_update do |record|
709
+ record.updated_at = Time.now
710
+ end
711
+ end
712
+
713
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
714
+ @machine.event :park
715
+
716
+ @record = @model.create(:updated_at => Time.now - 1)
717
+ @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
718
+
719
+ @timestamp = @record.updated_at
720
+ @transition.perform
721
+ end
722
+
723
+ def test_should_update_record
724
+ assert_not_equal @timestamp, @record.updated_at
725
+ end
726
+ end
727
+
728
+ class MachineWithDirtyAttributesTest < BaseTestCase
729
+ def setup
730
+ @model = new_model
731
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
732
+ @machine.event :ignite
733
+ @machine.state :idling
734
+
735
+ @record = @model.create
736
+
737
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
738
+ @transition.perform(false)
739
+ end
740
+
741
+ def test_should_include_state_in_changed_attributes
742
+ assert_equal %w(state), @record.changed
743
+ end
744
+
745
+ def test_should_track_attribute_change
746
+ assert_equal %w(parked idling), @record.changes['state']
747
+ end
748
+
749
+ def test_should_not_reset_changes_on_multiple_transitions
750
+ transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling)
751
+ transition.perform(false)
752
+
753
+ assert_equal %w(parked idling), @record.changes['state']
754
+ end
755
+
756
+ def test_should_not_have_changes_when_loaded_from_database
757
+ record = @model.find(@record.id)
758
+ assert !record.changed?
759
+ end
760
+ end
761
+
762
+ class MachineWithDirtyAttributesDuringLoopbackTest < BaseTestCase
763
+ def setup
764
+ @model = new_model
765
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
766
+ @machine.event :park
767
+
768
+ @record = @model.create
769
+
770
+ @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
771
+ @transition.perform(false)
772
+ end
773
+
774
+ def test_should_not_include_state_in_changed_attributes
775
+ assert_equal [], @record.changed
776
+ end
777
+
778
+ def test_should_not_track_attribute_changes
779
+ assert_equal nil, @record.changes['state']
780
+ end
781
+ end
782
+
783
+ class MachineWithDirtyAttributesAndCustomAttributeTest < BaseTestCase
784
+ def setup
785
+ @model = new_model do
786
+ key :status, String
787
+ end
788
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
789
+ @machine.event :ignite
790
+ @machine.state :idling
791
+
792
+ @record = @model.create
793
+
794
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
795
+ @transition.perform(false)
796
+ end
797
+
798
+ def test_should_include_state_in_changed_attributes
799
+ assert_equal %w(status), @record.changed
800
+ end
801
+
802
+ def test_should_track_attribute_change
803
+ assert_equal %w(parked idling), @record.changes['status']
804
+ end
805
+
806
+ def test_should_not_reset_changes_on_multiple_transitions
807
+ transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling)
808
+ transition.perform(false)
809
+
810
+ assert_equal %w(parked idling), @record.changes['status']
811
+ end
812
+ end
813
+
814
+ class MachineWithDirtyAttributeAndCustomAttributesDuringLoopbackTest < BaseTestCase
815
+ def setup
816
+ @model = new_model do
817
+ key :status, String
818
+ end
819
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
820
+ @machine.event :park
821
+
822
+ @record = @model.create
823
+
824
+ @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
825
+ @transition.perform(false)
826
+ end
827
+
828
+ def test_should_not_include_state_in_changed_attributes
829
+ assert_equal [], @record.changed
830
+ end
831
+
832
+ def test_should_not_track_attribute_changes
833
+ assert_equal nil, @record.changes['status']
834
+ end
835
+ end
836
+
837
+ class MachineWithDirtyAttributeAndStateEventsTest < BaseTestCase
838
+ def setup
839
+ @model = new_model
840
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
841
+ @machine.event :ignite
842
+
843
+ @record = @model.create
844
+ @record.state_event = 'ignite'
845
+ end
846
+
847
+ def test_should_not_include_state_in_changed_attributes
848
+ assert_equal [], @record.changed
849
+ end
850
+
851
+ def test_should_not_track_attribute_change
852
+ assert_equal nil, @record.changes['state']
853
+ end
854
+ end
855
+
856
+ class MachineWithCallbacksTest < BaseTestCase
857
+ def setup
858
+ @model = new_model
859
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
860
+ @machine.other_states :idling
861
+ @machine.event :ignite
862
+
863
+ @record = @model.new(:state => 'parked')
864
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
865
+ end
866
+
867
+ def test_should_run_before_callbacks
868
+ called = false
869
+ @machine.before_transition {called = true}
870
+
871
+ @transition.perform
872
+ assert called
873
+ end
874
+
875
+ def test_should_pass_record_to_before_callbacks_with_one_argument
876
+ record = nil
877
+ @machine.before_transition {|arg| record = arg}
878
+
879
+ @transition.perform
880
+ assert_equal @record, record
881
+ end
882
+
883
+ def test_should_pass_record_and_transition_to_before_callbacks_with_multiple_arguments
884
+ callback_args = nil
885
+ @machine.before_transition {|*args| callback_args = args}
886
+
887
+ @transition.perform
888
+ assert_equal [@record, @transition], callback_args
889
+ end
890
+
891
+ def test_should_run_before_callbacks_outside_the_context_of_the_record
892
+ context = nil
893
+ @machine.before_transition {context = self}
894
+
895
+ @transition.perform
896
+ assert_equal self, context
897
+ end
898
+
899
+ def test_should_run_after_callbacks
900
+ called = false
901
+ @machine.after_transition {called = true}
902
+
903
+ @transition.perform
904
+ assert called
905
+ end
906
+
907
+ def test_should_pass_record_to_after_callbacks_with_one_argument
908
+ record = nil
909
+ @machine.after_transition {|arg| record = arg}
910
+
911
+ @transition.perform
912
+ assert_equal @record, record
913
+ end
914
+
915
+ def test_should_pass_record_and_transition_to_after_callbacks_with_multiple_arguments
916
+ callback_args = nil
917
+ @machine.after_transition {|*args| callback_args = args}
918
+
919
+ @transition.perform
920
+ assert_equal [@record, @transition], callback_args
921
+ end
922
+
923
+ def test_should_run_after_callbacks_outside_the_context_of_the_record
924
+ context = nil
925
+ @machine.after_transition {context = self}
926
+
927
+ @transition.perform
928
+ assert_equal self, context
929
+ end
930
+
931
+ def test_should_run_after_callbacks_if_model_callback_added_prior_to_state_machine_definition
932
+ model = new_model do
933
+ after_save { nil }
934
+ end
935
+ machine = StateMachine::Machine.new(model, :initial => :parked)
936
+ machine.other_states :idling
937
+ machine.event :ignite
938
+ after_called = false
939
+ machine.after_transition {after_called = true}
940
+
941
+ record = model.new(:state => 'parked')
942
+ transition = StateMachine::Transition.new(record, machine, :ignite, :parked, :idling)
943
+ transition.perform
944
+ assert_equal true, after_called
945
+ end
946
+
947
+ def test_should_run_around_callbacks
948
+ before_called = false
949
+ after_called = false
950
+ ensure_called = 0
951
+ @machine.around_transition do |block|
952
+ before_called = true
953
+ begin
954
+ block.call
955
+ ensure
956
+ ensure_called += 1
957
+ end
958
+ after_called = true
959
+ end
960
+
961
+ @transition.perform
962
+ assert before_called
963
+ assert after_called
964
+ assert_equal ensure_called, 1
965
+ end
966
+
967
+ def test_should_include_transition_states_in_known_states
968
+ @machine.before_transition :to => :first_gear, :do => lambda {}
969
+
970
+ assert_equal [:parked, :idling, :first_gear], @machine.states.map {|state| state.name}
971
+ end
972
+
973
+ def test_should_allow_symbolic_callbacks
974
+ callback_args = nil
975
+
976
+ klass = class << @record; self; end
977
+ klass.send(:define_method, :after_ignite) do |*args|
978
+ callback_args = args
979
+ end
980
+
981
+ @machine.before_transition(:after_ignite)
982
+
983
+ @transition.perform
984
+ assert_equal [@transition], callback_args
985
+ end
986
+
987
+ def test_should_allow_string_callbacks
988
+ class << @record
989
+ attr_reader :callback_result
990
+ end
991
+
992
+ @machine.before_transition('@callback_result = [1, 2, 3]')
993
+ @transition.perform
994
+
995
+ assert_equal [1, 2, 3], @record.callback_result
996
+ end
997
+
998
+ def test_should_run_in_expected_order
999
+ expected = [
1000
+ :before_transition, :before_validation, :after_validation,
1001
+ :before_save, :before_create, :after_create, :after_save,
1002
+ :after_transition
1003
+ ]
1004
+
1005
+ callbacks = []
1006
+ @model.before_validation { callbacks << :before_validation }
1007
+ @model.after_validation { callbacks << :after_validation }
1008
+ @model.before_save { callbacks << :before_save }
1009
+ @model.before_create { callbacks << :before_create }
1010
+ @model.after_create { callbacks << :after_create }
1011
+ @model.after_save { callbacks << :after_save }
1012
+
1013
+ @machine.before_transition { callbacks << :before_transition }
1014
+ @machine.after_transition { callbacks << :after_transition }
1015
+
1016
+ @transition.perform
1017
+
1018
+ assert_equal expected, callbacks
1019
+ end
1020
+ end
1021
+
1022
+ class MachineWithFailedBeforeCallbacksTest < BaseTestCase
1023
+ def setup
1024
+ @callbacks = []
1025
+
1026
+ @model = new_model
1027
+ @machine = StateMachine::Machine.new(@model)
1028
+ @machine.state :parked, :idling
1029
+ @machine.event :ignite
1030
+ @machine.before_transition {@callbacks << :before_1; false}
1031
+ @machine.before_transition {@callbacks << :before_2}
1032
+ @machine.after_transition {@callbacks << :after}
1033
+ @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after}
1034
+
1035
+ @record = @model.new(:state => 'parked')
1036
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
1037
+ @result = @transition.perform
1038
+ end
1039
+
1040
+ if !defined?(MongoMapper::Version) || MongoMapper::Version =~ /^0\.[5-8]\./
1041
+ def test_should_be_successful
1042
+ assert @result
1043
+ end
1044
+
1045
+ def test_should_change_current_state
1046
+ assert_equal 'idling', @record.state
1047
+ end
1048
+
1049
+ def test_should_run_action
1050
+ assert !@record.new_record?
1051
+ end
1052
+
1053
+ def test_should_run_further_callbacks
1054
+ assert_equal [:before_1, :before_2, :around_before, :around_after, :after], @callbacks
1055
+ end
1056
+ else
1057
+ def test_should_not_be_successful
1058
+ assert !@result
1059
+ end
1060
+
1061
+ def test_should_not_change_current_state
1062
+ assert_equal 'parked', @record.state
1063
+ end
1064
+
1065
+ def test_should_not_run_action
1066
+ assert @record.new_record?
1067
+ end
1068
+
1069
+ def test_should_not_run_further_callbacks
1070
+ assert_equal [:before_1], @callbacks
1071
+ end
1072
+ end
1073
+ end
1074
+
1075
+ class MachineNestedActionTest < BaseTestCase
1076
+ def setup
1077
+ @callbacks = []
1078
+
1079
+ @model = new_model
1080
+ @machine = StateMachine::Machine.new(@model)
1081
+ @machine.event :ignite do
1082
+ transition :parked => :idling
1083
+ end
1084
+
1085
+ @record = @model.new(:state => 'parked')
1086
+ end
1087
+
1088
+ def test_should_allow_transition_prior_to_creation_if_skipping_action
1089
+ record = @record
1090
+ @model.before_create { record.ignite(false) }
1091
+ result = @record.save
1092
+
1093
+ assert_equal true, result
1094
+ assert_equal "idling", @record.state
1095
+ @record = @model.find(@record.id)
1096
+ assert_equal "idling", @record.state
1097
+ end
1098
+
1099
+ def test_should_allow_transition_after_creation
1100
+ record = @record
1101
+ @model.after_create { record.ignite }
1102
+ result = @record.save
1103
+
1104
+ assert_equal true, result
1105
+ assert_equal "idling", @record.state
1106
+ @record = @model.find(@record.id)
1107
+ assert_equal "idling", @record.state
1108
+ end
1109
+ end
1110
+
1111
+ class MachineWithFailedActionTest < BaseTestCase
1112
+ def setup
1113
+ @model = new_model do
1114
+ validates_numericality_of :state
1115
+ end
1116
+
1117
+ @machine = StateMachine::Machine.new(@model)
1118
+ @machine.state :parked, :idling
1119
+ @machine.event :ignite
1120
+
1121
+ @callbacks = []
1122
+ @machine.before_transition {@callbacks << :before}
1123
+ @machine.after_transition {@callbacks << :after}
1124
+ @machine.after_failure {@callbacks << :after_failure}
1125
+ @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after}
1126
+
1127
+ @record = @model.new(:state => 'parked')
1128
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
1129
+ @result = @transition.perform
1130
+ end
1131
+
1132
+ def test_should_not_be_successful
1133
+ assert !@result
1134
+ end
1135
+
1136
+ def test_should_not_change_current_state
1137
+ assert_equal 'parked', @record.state
1138
+ end
1139
+
1140
+ def test_should_not_save_record
1141
+ assert @record.new_record?
1142
+ end
1143
+
1144
+ def test_should_run_before_callbacks_and_after_callbacks_with_failures
1145
+ assert_equal [:before, :around_before, :after_failure], @callbacks
1146
+ end
1147
+ end
1148
+
1149
+ class MachineWithFailedAfterCallbacksTest < BaseTestCase
1150
+ def setup
1151
+ @callbacks = []
1152
+
1153
+ @model = new_model
1154
+ @machine = StateMachine::Machine.new(@model)
1155
+ @machine.state :parked, :idling
1156
+ @machine.event :ignite
1157
+ @machine.after_transition {@callbacks << :after_1; false}
1158
+ @machine.after_transition {@callbacks << :after_2}
1159
+ @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after}
1160
+
1161
+ @record = @model.new(:state => 'parked')
1162
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
1163
+ @result = @transition.perform
1164
+ end
1165
+
1166
+ def test_should_be_successful
1167
+ assert @result
1168
+ end
1169
+
1170
+ def test_should_change_current_state
1171
+ assert_equal 'idling', @record.state
1172
+ end
1173
+
1174
+ def test_should_save_record
1175
+ assert !@record.new_record?
1176
+ end
1177
+
1178
+ if !defined?(MongoMapper::Version) || MongoMapper::Version =~ /^0\.[5-8]\./
1179
+ def test_should_still_run_further_after_callbacks
1180
+ assert_equal [:around_before, :around_after, :after_1, :after_2], @callbacks
1181
+ end
1182
+ else
1183
+ def test_should_not_run_further_after_callbacks
1184
+ assert_equal [:around_before, :around_after, :after_1], @callbacks
1185
+ end
1186
+ end
1187
+ end
1188
+
1189
+ class MachineWithValidationsTest < BaseTestCase
1190
+ def setup
1191
+ @model = new_model
1192
+ @machine = StateMachine::Machine.new(@model)
1193
+ @machine.state :parked
1194
+
1195
+ @record = @model.new
1196
+ end
1197
+
1198
+ def test_should_invalidate_using_errors
1199
+ I18n.backend = I18n::Backend::Simple.new if Object.const_defined?(:ActiveModel)
1200
+ @record.state = 'parked'
1201
+
1202
+ @machine.invalidate(@record, :state, :invalid_transition, [[:event, 'park']])
1203
+ assert_equal ['State cannot transition via "park"'], @record.errors.full_messages
1204
+ end
1205
+
1206
+ def test_should_auto_prefix_custom_attributes_on_invalidation
1207
+ @machine.invalidate(@record, :event, :invalid)
1208
+
1209
+ assert_equal ['State event is invalid'], @record.errors.full_messages
1210
+ end
1211
+
1212
+ def test_should_clear_errors_on_reset
1213
+ @record.state = 'parked'
1214
+ @record.errors.add(:state, 'is invalid')
1215
+
1216
+ @machine.reset(@record)
1217
+ assert_equal [], @record.errors.full_messages
1218
+ end
1219
+
1220
+ def test_should_be_valid_if_state_is_known
1221
+ @record.state = 'parked'
1222
+
1223
+ assert @record.valid?
1224
+ end
1225
+
1226
+ def test_should_not_be_valid_if_state_is_unknown
1227
+ @record.state = 'invalid'
1228
+
1229
+ assert !@record.valid?
1230
+ assert_equal ['State is invalid'], @record.errors.full_messages
1231
+ end
1232
+ end
1233
+
1234
+ class MachineWithValidationsAndCustomAttributeTest < BaseTestCase
1235
+ def setup
1236
+ @model = new_model
1237
+ @machine = StateMachine::Machine.new(@model, :status, :attribute => :state)
1238
+ @machine.state :parked
1239
+
1240
+ @record = @model.new
1241
+ end
1242
+
1243
+ def test_should_add_validation_errors_to_custom_attribute
1244
+ @record.state = 'invalid'
1245
+
1246
+ assert !@record.valid?
1247
+ assert_equal ['State is invalid'], @record.errors.full_messages
1248
+
1249
+ @record.state = 'parked'
1250
+ assert @record.valid?
1251
+ end
1252
+ end
1253
+
1254
+ class MachineErrorsTest < BaseTestCase
1255
+ def setup
1256
+ @model = new_model
1257
+ @machine = StateMachine::Machine.new(@model)
1258
+ @record = @model.new
1259
+ end
1260
+
1261
+ def test_should_be_able_to_describe_current_errors
1262
+ @record.errors.add(:id, 'cannot be blank')
1263
+ @record.errors.add(:state, 'is invalid')
1264
+ assert_equal ['Id cannot be blank', 'State is invalid'], @machine.errors_for(@record).split(', ').sort
1265
+ end
1266
+
1267
+ def test_should_describe_as_halted_with_no_errors
1268
+ assert_equal 'Transition halted', @machine.errors_for(@record)
1269
+ end
1270
+ end
1271
+
1272
+ class MachineWithStateDrivenValidationsTest < BaseTestCase
1273
+ def setup
1274
+ @model = new_model do
1275
+ attr_accessor :seatbealt
1276
+ end
1277
+
1278
+ @machine = StateMachine::Machine.new(@model)
1279
+ @machine.state :first_gear do
1280
+ validates_presence_of :seatbelt, :key => :first_gear
1281
+ end
1282
+ @machine.state :second_gear do
1283
+ validates_presence_of :seatbelt, :key => :second_gear
1284
+ end
1285
+ @machine.other_states :parked
1286
+ end
1287
+
1288
+ def test_should_be_valid_if_validation_fails_outside_state_scope
1289
+ record = @model.new(:state => 'parked', :seatbelt => nil)
1290
+ assert record.valid?
1291
+ end
1292
+
1293
+ def test_should_be_invalid_if_validation_fails_within_state_scope
1294
+ record = @model.new(:state => 'first_gear', :seatbelt => nil)
1295
+ assert !record.valid?
1296
+ end
1297
+
1298
+ def test_should_be_valid_if_validation_succeeds_within_state_scope
1299
+ record = @model.new(:state => 'second_gear', :seatbelt => true)
1300
+ assert record.valid?
1301
+ end
1302
+ end
1303
+
1304
+ class MachineWithEventAttributesOnValidationTest < BaseTestCase
1305
+ def setup
1306
+ @model = new_model
1307
+ @machine = StateMachine::Machine.new(@model)
1308
+ @machine.event :ignite do
1309
+ transition :parked => :idling
1310
+ end
1311
+
1312
+ @record = @model.new
1313
+ @record.state = 'parked'
1314
+ @record.state_event = 'ignite'
1315
+ end
1316
+
1317
+ def test_should_fail_if_event_is_invalid
1318
+ @record.state_event = 'invalid'
1319
+ assert !@record.valid?
1320
+ assert_equal ['State event is invalid'], @record.errors.full_messages
1321
+ end
1322
+
1323
+ def test_should_fail_if_event_has_no_transition
1324
+ @record.state = 'idling'
1325
+ assert !@record.valid?
1326
+ assert_equal ['State event cannot transition when idling'], @record.errors.full_messages
1327
+ end
1328
+
1329
+ def test_should_be_successful_if_event_has_transition
1330
+ assert @record.valid?
1331
+ end
1332
+
1333
+ def test_should_run_before_callbacks
1334
+ ran_callback = false
1335
+ @machine.before_transition { ran_callback = true }
1336
+
1337
+ @record.valid?
1338
+ assert ran_callback
1339
+ end
1340
+
1341
+ def test_should_run_around_callbacks_before_yield
1342
+ ran_callback = false
1343
+ @machine.around_transition {|block| ran_callback = true; block.call }
1344
+
1345
+ begin
1346
+ @record.valid?
1347
+ rescue ArgumentError
1348
+ raise if StateMachine::Transition.pause_supported?
1349
+ end
1350
+ assert ran_callback
1351
+ end
1352
+
1353
+ def test_should_persist_new_state
1354
+ @record.valid?
1355
+ assert_equal 'idling', @record.state
1356
+ end
1357
+
1358
+ def test_should_not_run_after_callbacks
1359
+ ran_callback = false
1360
+ @machine.after_transition { ran_callback = true }
1361
+
1362
+ @record.valid?
1363
+ assert !ran_callback
1364
+ end
1365
+
1366
+ def test_should_not_run_after_callbacks_with_failures_disabled_if_validation_fails
1367
+ @model.class_eval do
1368
+ attr_accessor :seatbelt
1369
+ validates_presence_of :seatbelt
1370
+ end
1371
+
1372
+ ran_callback = false
1373
+ @machine.after_transition { ran_callback = true }
1374
+
1375
+ @record.valid?
1376
+ assert !ran_callback
1377
+ end
1378
+
1379
+ def test_should_not_run_around_callbacks_after_yield
1380
+ ran_callback = false
1381
+ @machine.around_transition {|block| block.call; ran_callback = true }
1382
+
1383
+ begin
1384
+ @record.valid?
1385
+ rescue ArgumentError
1386
+ raise if StateMachine::Transition.pause_supported?
1387
+ end
1388
+ assert !ran_callback
1389
+ end
1390
+
1391
+ def test_should_not_run_around_callbacks_after_yield_with_failures_disabled_if_validation_fails
1392
+ @model.class_eval do
1393
+ attr_accessor :seatbelt
1394
+ validates_presence_of :seatbelt
1395
+ end
1396
+
1397
+ ran_callback = false
1398
+ @machine.around_transition {|block| block.call; ran_callback = true }
1399
+
1400
+ @record.valid?
1401
+ assert !ran_callback
1402
+ end
1403
+
1404
+ def test_should_run_failure_callbacks_if_validation_fails
1405
+ @model.class_eval do
1406
+ attr_accessor :seatbelt
1407
+ validates_presence_of :seatbelt
1408
+ end
1409
+
1410
+ ran_callback = false
1411
+ @machine.after_failure { ran_callback = true }
1412
+
1413
+ @record.valid?
1414
+ assert ran_callback
1415
+ end
1416
+ end
1417
+
1418
+ class MachineWithEventAttributesOnSaveTest < BaseTestCase
1419
+ def setup
1420
+ @model = new_model
1421
+ @machine = StateMachine::Machine.new(@model)
1422
+ @machine.event :ignite do
1423
+ transition :parked => :idling
1424
+ end
1425
+
1426
+ @record = @model.new
1427
+ @record.state = 'parked'
1428
+ @record.state_event = 'ignite'
1429
+ end
1430
+
1431
+ def test_should_fail_if_event_is_invalid
1432
+ @record.state_event = 'invalid'
1433
+ assert_equal false, @record.save
1434
+ end
1435
+
1436
+ def test_should_fail_if_event_has_no_transition
1437
+ @record.state = 'idling'
1438
+ assert_equal false, @record.save
1439
+ end
1440
+
1441
+ def test_should_be_successful_if_event_has_transition
1442
+ assert_equal true, @record.save
1443
+ end
1444
+
1445
+ def test_should_run_before_callbacks
1446
+ ran_callback = false
1447
+ @machine.before_transition { ran_callback = true }
1448
+
1449
+ @record.save
1450
+ assert ran_callback
1451
+ end
1452
+
1453
+ def test_should_run_before_callbacks_once
1454
+ before_count = 0
1455
+ @machine.before_transition { before_count += 1 }
1456
+
1457
+ @record.save
1458
+ assert_equal 1, before_count
1459
+ end
1460
+
1461
+ def test_should_run_around_callbacks_before_yield
1462
+ ran_callback = false
1463
+ @machine.around_transition {|block| ran_callback = true; block.call }
1464
+
1465
+ @record.save
1466
+ assert ran_callback
1467
+ end
1468
+
1469
+ def test_should_run_around_callbacks_before_yield_once
1470
+ around_before_count = 0
1471
+ @machine.around_transition {|block| around_before_count += 1; block.call }
1472
+
1473
+ @record.save
1474
+ assert_equal 1, around_before_count
1475
+ end
1476
+
1477
+ def test_should_persist_new_state
1478
+ @record.save
1479
+ assert_equal 'idling', @record.state
1480
+ end
1481
+
1482
+ def test_should_run_after_callbacks
1483
+ ran_callback = false
1484
+ @machine.after_transition { ran_callback = true }
1485
+
1486
+ @record.save
1487
+ assert ran_callback
1488
+ end
1489
+
1490
+ def test_should_not_run_after_callbacks_with_failures_disabled_if_fails
1491
+ @model.class_eval do
1492
+ validates_numericality_of :state
1493
+ end
1494
+
1495
+ ran_callback = false
1496
+ @machine.after_transition { ran_callback = true }
1497
+
1498
+ begin; @record.save; rescue; end
1499
+ assert !ran_callback
1500
+ end
1501
+
1502
+ def test_should_run_failure_callbacks__if_fails
1503
+ @model.class_eval do
1504
+ validates_numericality_of :state
1505
+ end
1506
+
1507
+ ran_callback = false
1508
+ @machine.after_failure { ran_callback = true }
1509
+
1510
+ begin; @record.save; rescue; end
1511
+ assert ran_callback
1512
+ end
1513
+
1514
+ def test_should_not_run_around_callbacks_with_failures_disabled_if_fails
1515
+ @model.class_eval do
1516
+ validates_numericality_of :state
1517
+ end
1518
+
1519
+ ran_callback = false
1520
+ @machine.around_transition {|block| block.call; ran_callback = true }
1521
+
1522
+ begin; @record.save; rescue; end
1523
+ assert !ran_callback
1524
+ end
1525
+
1526
+ def test_should_run_around_callbacks_after_yield
1527
+ ran_callback = false
1528
+ @machine.around_transition {|block| block.call; ran_callback = true }
1529
+
1530
+ @record.save
1531
+ assert ran_callback
1532
+ end
1533
+
1534
+ def test_should_allow_additional_transitions_to_new_state_in_after_transitions
1535
+ @machine.event :park do
1536
+ transition :idling => :parked
1537
+ end
1538
+
1539
+ @machine.after_transition(:on => :ignite) { @record.park }
1540
+
1541
+ @record.save
1542
+ assert_equal 'parked', @record.state
1543
+
1544
+ @record = @model.find(@record.id)
1545
+ assert_equal 'parked', @record.state
1546
+ end
1547
+
1548
+ def test_should_allow_additional_transitions_to_previous_state_in_after_transitions
1549
+ @machine.event :shift_up do
1550
+ transition :idling => :first_gear
1551
+ end
1552
+
1553
+ @machine.after_transition(:on => :ignite) { @record.shift_up }
1554
+
1555
+ @record.save
1556
+ assert_equal 'first_gear', @record.state
1557
+
1558
+ @record = @model.find(@record.id)
1559
+ assert_equal 'first_gear', @record.state
1560
+ end
1561
+ end
1562
+
1563
+ if defined?(MongoMapper::Version) && MongoMapper::Version !~ /^0\.[5-8]\./
1564
+ class MachineWithEventAttributesOnAutosaveTest < BaseTestCase
1565
+ def setup
1566
+ @vehicle_model = new_model(:vehicle) do
1567
+ belongs_to :owner, :class_name => 'MongoMapperTest::Owner'
1568
+ end
1569
+ MongoMapperTest.const_set('Vehicle', @vehicle_model)
1570
+
1571
+ @owner_model = new_model(:owner)
1572
+ MongoMapperTest.const_set('Owner', @owner_model)
1573
+
1574
+ machine = StateMachine::Machine.new(@vehicle_model)
1575
+ machine.event :ignite do
1576
+ transition :parked => :idling
1577
+ end
1578
+
1579
+ @owner = @owner_model.create
1580
+ @vehicle = @vehicle_model.create(:state => 'parked', :owner_id => @owner.id)
1581
+ end
1582
+
1583
+ def test_should_persist_many_association
1584
+ @owner_model.many :vehicles, :class_name => 'MongoMapperTest::Vehicle', :autosave => true
1585
+ @owner.vehicles[0].state_event = 'ignite'
1586
+ @owner.save
1587
+
1588
+ @vehicle.reload
1589
+ assert_equal 'idling', @vehicle.state
1590
+ end
1591
+
1592
+ def teardown
1593
+ MongoMapperTest.class_eval do
1594
+ remove_const('Vehicle')
1595
+ remove_const('Owner')
1596
+ end
1597
+ super
1598
+ end
1599
+ end
1600
+ end
1601
+
1602
+ class MachineWithEventAttributesOnSaveBangTest < BaseTestCase
1603
+ def setup
1604
+ @model = new_model
1605
+ @machine = StateMachine::Machine.new(@model)
1606
+ @machine.event :ignite do
1607
+ transition :parked => :idling
1608
+ end
1609
+
1610
+ @record = @model.new
1611
+ @record.state = 'parked'
1612
+ @record.state_event = 'ignite'
1613
+ end
1614
+
1615
+ def test_should_fail_if_event_is_invalid
1616
+ @record.state_event = 'invalid'
1617
+ assert_raise(MongoMapper::DocumentNotValid) { @record.save! }
1618
+ end
1619
+
1620
+ def test_should_fail_if_event_has_no_transition
1621
+ @record.state = 'idling'
1622
+ assert_raise(MongoMapper::DocumentNotValid) { @record.save! }
1623
+ end
1624
+
1625
+ def test_should_be_successful_if_event_has_transition
1626
+ assert_equal true, @record.save!
1627
+ end
1628
+
1629
+ def test_should_run_before_callbacks
1630
+ ran_callback = false
1631
+ @machine.before_transition { ran_callback = true }
1632
+
1633
+ @record.save!
1634
+ assert ran_callback
1635
+ end
1636
+
1637
+ def test_should_run_before_callbacks_once
1638
+ before_count = 0
1639
+ @machine.before_transition { before_count += 1 }
1640
+
1641
+ @record.save!
1642
+ assert_equal 1, before_count
1643
+ end
1644
+
1645
+ def test_should_run_around_callbacks_before_yield
1646
+ ran_callback = false
1647
+ @machine.around_transition {|block| ran_callback = true; block.call }
1648
+
1649
+ @record.save!
1650
+ assert ran_callback
1651
+ end
1652
+
1653
+ def test_should_run_around_callbacks_before_yield_once
1654
+ around_before_count = 0
1655
+ @machine.around_transition {|block| around_before_count += 1; block.call }
1656
+
1657
+ @record.save!
1658
+ assert_equal 1, around_before_count
1659
+ end
1660
+
1661
+ def test_should_persist_new_state
1662
+ @record.save!
1663
+ assert_equal 'idling', @record.state
1664
+ end
1665
+
1666
+ def test_should_run_after_callbacks
1667
+ ran_callback = false
1668
+ @machine.after_transition { ran_callback = true }
1669
+
1670
+ @record.save!
1671
+ assert ran_callback
1672
+ end
1673
+
1674
+ def test_should_run_around_callbacks_after_yield
1675
+ ran_callback = false
1676
+ @machine.around_transition {|block| block.call; ran_callback = true }
1677
+
1678
+ @record.save!
1679
+ assert ran_callback
1680
+ end
1681
+ end
1682
+
1683
+ class MachineWithEventAttributesOnCustomActionTest < BaseTestCase
1684
+ def setup
1685
+ @superclass = new_model do
1686
+ def persist
1687
+ create_or_update
1688
+ end
1689
+ end
1690
+ @model = Class.new(@superclass)
1691
+ @machine = StateMachine::Machine.new(@model, :action => :persist)
1692
+ @machine.event :ignite do
1693
+ transition :parked => :idling
1694
+ end
1695
+
1696
+ @record = @model.new
1697
+ @record.state = 'parked'
1698
+ @record.state_event = 'ignite'
1699
+ end
1700
+
1701
+ def test_should_not_transition_on_valid?
1702
+ @record.valid?
1703
+ assert_equal 'parked', @record.state
1704
+ end
1705
+
1706
+ def test_should_not_transition_on_save
1707
+ @record.save
1708
+ assert_equal 'parked', @record.state
1709
+ end
1710
+
1711
+ def test_should_not_transition_on_save!
1712
+ @record.save!
1713
+ assert_equal 'parked', @record.state
1714
+ end
1715
+
1716
+ def test_should_transition_on_custom_action
1717
+ @record.persist
1718
+ assert_equal 'idling', @record.state
1719
+ end
1720
+ end
1721
+
1722
+ class MachineWithScopesTest < BaseTestCase
1723
+ def setup
1724
+ @model = new_model
1725
+ @machine = StateMachine::Machine.new(@model)
1726
+ @machine.state :parked, :first_gear
1727
+ @machine.state :idling, :value => lambda {'idling'}
1728
+ end
1729
+
1730
+ def test_should_create_singular_with_scope
1731
+ assert @model.respond_to?(:with_state)
1732
+ end
1733
+
1734
+ def test_should_only_include_records_with_state_in_singular_with_scope
1735
+ parked = @model.create :state => 'parked'
1736
+ @model.create :state => 'idling'
1737
+
1738
+ assert_equal [parked], @model.with_state(:parked).to_a
1739
+ end
1740
+
1741
+ def test_should_create_plural_with_scope
1742
+ assert @model.respond_to?(:with_states)
1743
+ end
1744
+
1745
+ def test_should_only_include_records_with_states_in_plural_with_scope
1746
+ parked = @model.create :state => 'parked'
1747
+ idling = @model.create :state => 'idling'
1748
+
1749
+ assert_equal [parked, idling], @model.with_states(:parked, :idling).to_a
1750
+ end
1751
+
1752
+ def test_should_allow_lookup_by_string_name
1753
+ parked = @model.create :state => 'parked'
1754
+ idling = @model.create :state => 'idling'
1755
+
1756
+ assert_equal [parked, idling], @model.with_states('parked', 'idling').to_a
1757
+ end
1758
+
1759
+ def test_should_create_singular_without_scope
1760
+ assert @model.respond_to?(:without_state)
1761
+ end
1762
+
1763
+ def test_should_only_include_records_without_state_in_singular_without_scope
1764
+ parked = @model.create :state => 'parked'
1765
+ idling = @model.create :state => 'idling'
1766
+
1767
+ assert_equal [parked], @model.without_state(:idling).to_a
1768
+ end
1769
+
1770
+ def test_should_create_plural_without_scope
1771
+ assert @model.respond_to?(:without_states)
1772
+ end
1773
+
1774
+ def test_should_only_include_records_without_states_in_plural_without_scope
1775
+ parked = @model.create :state => 'parked'
1776
+ idling = @model.create :state => 'idling'
1777
+ first_gear = @model.create :state => 'first_gear'
1778
+
1779
+ assert_equal [parked, idling], @model.without_states(:first_gear).to_a
1780
+ end
1781
+
1782
+ if defined?(MongoMapper::Version) && !(MongoMapper::Version =~ /^0\.[5-7]\./)
1783
+ def test_should_allow_chaining_scopes
1784
+ parked = @model.create :state => 'parked'
1785
+ idling = @model.create :state => 'idling'
1786
+
1787
+ assert_equal [idling], @model.without_state(:parked).with_state(:idling).all
1788
+ end
1789
+ end
1790
+ end
1791
+
1792
+ class MachineWithScopesAndOwnerSubclassTest < BaseTestCase
1793
+ def setup
1794
+ @model = new_model
1795
+ @machine = StateMachine::Machine.new(@model, :state)
1796
+
1797
+ @subclass = Class.new(@model)
1798
+ @subclass_machine = @subclass.state_machine(:state) {}
1799
+ @subclass_machine.state :parked, :idling, :first_gear
1800
+ end
1801
+
1802
+ def test_should_only_include_records_with_subclass_states_in_with_scope
1803
+ parked = @subclass.create :state => 'parked'
1804
+ idling = @subclass.create :state => 'idling'
1805
+
1806
+ assert_equal [parked, idling], @subclass.with_states(:parked, :idling).to_a
1807
+ end
1808
+
1809
+ def test_should_only_include_records_without_subclass_states_in_without_scope
1810
+ parked = @subclass.create :state => 'parked'
1811
+ idling = @subclass.create :state => 'idling'
1812
+ first_gear = @subclass.create :state => 'first_gear'
1813
+
1814
+ assert_equal [parked, idling], @subclass.without_states(:first_gear).to_a
1815
+ end
1816
+ end
1817
+
1818
+ class MachineWithComplexPluralizationScopesTest < BaseTestCase
1819
+ def setup
1820
+ @model = new_model
1821
+ @machine = StateMachine::Machine.new(@model, :status)
1822
+ end
1823
+
1824
+ def test_should_create_singular_with_scope
1825
+ assert @model.respond_to?(:with_status)
1826
+ end
1827
+
1828
+ def test_should_create_plural_with_scope
1829
+ assert @model.respond_to?(:with_statuses)
1830
+ end
1831
+ end
1832
+
1833
+ if defined?(ActiveModel)
1834
+ class MachineWithInternationalizationTest < BaseTestCase
1835
+ def setup
1836
+ I18n.backend = I18n::Backend::Simple.new
1837
+
1838
+ # Initialize the backend
1839
+ StateMachine::Machine.new(new_model)
1840
+ I18n.backend.translate(:en, 'mongo_mapper.errors.messages.invalid_transition', :event => 'ignite', :value => 'idling')
1841
+
1842
+ @model = new_model
1843
+ end
1844
+
1845
+ def test_should_use_defaults
1846
+ I18n.backend.store_translations(:en, {
1847
+ :mongo_mapper => {:errors => {:messages => {:invalid_transition => 'cannot %{event}'}}}
1848
+ })
1849
+
1850
+ machine = StateMachine::Machine.new(@model)
1851
+ machine.state :parked, :idling
1852
+ machine.event :ignite
1853
+
1854
+ record = @model.new(:state => 'idling')
1855
+
1856
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
1857
+ assert_equal ['State cannot ignite'], record.errors.full_messages
1858
+ end
1859
+
1860
+ def test_should_allow_customized_error_key
1861
+ I18n.backend.store_translations(:en, {
1862
+ :mongo_mapper => {:errors => {:messages => {:bad_transition => 'cannot %{event}'}}}
1863
+ })
1864
+
1865
+ machine = StateMachine::Machine.new(@model, :messages => {:invalid_transition => :bad_transition})
1866
+ machine.state :parked, :idling
1867
+
1868
+ record = @model.new(:state => 'idling')
1869
+
1870
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
1871
+ assert_equal ['State cannot ignite'], record.errors.full_messages
1872
+ end
1873
+
1874
+ def test_should_allow_customized_error_string
1875
+ machine = StateMachine::Machine.new(@model, :messages => {:invalid_transition => 'cannot %{event}'})
1876
+ machine.state :parked, :idling
1877
+
1878
+ record = @model.new(:state => 'idling')
1879
+
1880
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
1881
+ assert_equal ['State cannot ignite'], record.errors.full_messages
1882
+ end
1883
+
1884
+ def test_should_allow_customized_state_key_scoped_to_class_and_machine
1885
+ I18n.backend.store_translations(:en, {
1886
+ :mongo_mapper => {:state_machines => {:'mongo_mapper_test/foo' => {:state => {:states => {:parked => 'shutdown'}}}}}
1887
+ })
1888
+
1889
+ machine = StateMachine::Machine.new(@model)
1890
+ machine.state :parked
1891
+
1892
+ assert_equal 'shutdown', machine.state(:parked).human_name
1893
+ end
1894
+
1895
+ def test_should_allow_customized_state_key_scoped_to_class
1896
+ I18n.backend.store_translations(:en, {
1897
+ :mongo_mapper => {:state_machines => {:'mongo_mapper_test/foo' => {:states => {:parked => 'shutdown'}}}}
1898
+ })
1899
+
1900
+ machine = StateMachine::Machine.new(@model)
1901
+ machine.state :parked
1902
+
1903
+ assert_equal 'shutdown', machine.state(:parked).human_name
1904
+ end
1905
+
1906
+ def test_should_allow_customized_state_key_scoped_to_machine
1907
+ I18n.backend.store_translations(:en, {
1908
+ :mongo_mapper => {:state_machines => {:state => {:states => {:parked => 'shutdown'}}}}
1909
+ })
1910
+
1911
+ machine = StateMachine::Machine.new(@model)
1912
+ machine.state :parked
1913
+
1914
+ assert_equal 'shutdown', machine.state(:parked).human_name
1915
+ end
1916
+
1917
+ def test_should_allow_customized_state_key_unscoped
1918
+ I18n.backend.store_translations(:en, {
1919
+ :mongo_mapper => {:state_machines => {:states => {:parked => 'shutdown'}}}
1920
+ })
1921
+
1922
+ machine = StateMachine::Machine.new(@model)
1923
+ machine.state :parked
1924
+
1925
+ assert_equal 'shutdown', machine.state(:parked).human_name
1926
+ end
1927
+
1928
+ def test_should_support_nil_state_key
1929
+ I18n.backend.store_translations(:en, {
1930
+ :mongo_mapper => {:state_machines => {:states => {:nil => 'empty'}}}
1931
+ })
1932
+
1933
+ machine = StateMachine::Machine.new(@model)
1934
+
1935
+ assert_equal 'empty', machine.state(nil).human_name
1936
+ end
1937
+
1938
+ def test_should_allow_customized_event_key_scoped_to_class_and_machine
1939
+ I18n.backend.store_translations(:en, {
1940
+ :mongo_mapper => {:state_machines => {:'mongo_mapper_test/foo' => {:state => {:events => {:park => 'stop'}}}}}
1941
+ })
1942
+
1943
+ machine = StateMachine::Machine.new(@model)
1944
+ machine.event :park
1945
+
1946
+ assert_equal 'stop', machine.event(:park).human_name
1947
+ end
1948
+
1949
+ def test_should_allow_customized_event_key_scoped_to_class
1950
+ I18n.backend.store_translations(:en, {
1951
+ :mongo_mapper => {:state_machines => {:'mongo_mapper_test/foo' => {:events => {:park => 'stop'}}}}
1952
+ })
1953
+
1954
+ machine = StateMachine::Machine.new(@model)
1955
+ machine.event :park
1956
+
1957
+ assert_equal 'stop', machine.event(:park).human_name
1958
+ end
1959
+
1960
+ def test_should_allow_customized_event_key_scoped_to_machine
1961
+ I18n.backend.store_translations(:en, {
1962
+ :mongo_mapper => {:state_machines => {:state => {:events => {:park => 'stop'}}}}
1963
+ })
1964
+
1965
+ machine = StateMachine::Machine.new(@model)
1966
+ machine.event :park
1967
+
1968
+ assert_equal 'stop', machine.event(:park).human_name
1969
+ end
1970
+
1971
+ def test_should_allow_customized_event_key_unscoped
1972
+ I18n.backend.store_translations(:en, {
1973
+ :mongo_mapper => {:state_machines => {:events => {:park => 'stop'}}}
1974
+ })
1975
+
1976
+ machine = StateMachine::Machine.new(@model)
1977
+ machine.event :park
1978
+
1979
+ assert_equal 'stop', machine.event(:park).human_name
1980
+ end
1981
+
1982
+ def test_should_only_add_locale_once_in_load_path
1983
+ assert_equal 1, I18n.load_path.select {|path| path =~ %r{mongo_mapper/locale\.rb$}}.length
1984
+
1985
+ # Create another MongoMapper model that will triger the i18n feature
1986
+ new_model
1987
+
1988
+ assert_equal 1, I18n.load_path.select {|path| path =~ %r{mongo_mapper/locale\.rb$}}.length
1989
+ end
1990
+
1991
+ def test_should_add_locale_to_beginning_of_load_path
1992
+ @original_load_path = I18n.load_path
1993
+ I18n.backend = I18n::Backend::Simple.new
1994
+
1995
+ app_locale = File.dirname(__FILE__) + '/../../files/en.yml'
1996
+ default_locale = File.dirname(__FILE__) + '/../../../lib/state_machine/integrations/mongo_mapper/locale.rb'
1997
+ I18n.load_path = [app_locale]
1998
+
1999
+ StateMachine::Machine.new(@model)
2000
+
2001
+ assert_equal [default_locale, app_locale].map {|path| File.expand_path(path)}, I18n.load_path.map {|path| File.expand_path(path)}
2002
+ ensure
2003
+ I18n.load_path = @original_load_path
2004
+ end
2005
+
2006
+ def test_should_prefer_other_locales_first
2007
+ @original_load_path = I18n.load_path
2008
+ I18n.backend = I18n::Backend::Simple.new
2009
+ I18n.load_path = [File.dirname(__FILE__) + '/../../files/en.yml']
2010
+
2011
+ machine = StateMachine::Machine.new(@model)
2012
+ machine.state :parked, :idling
2013
+ machine.event :ignite
2014
+
2015
+ record = @model.new(:state => 'idling')
2016
+
2017
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
2018
+ assert_equal ['State cannot transition'], record.errors.full_messages
2019
+ ensure
2020
+ I18n.load_path = @original_load_path
2021
+ end
2022
+ end
2023
+ else
2024
+ $stderr.puts 'Skipping MongoMapper I18n tests.'
2025
+ end
2026
+ end