PageRenderTime 50ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/test/unit/machine_collection_test.rb

https://github.com/amatsuda/state_machine
Ruby | 565 lines | 442 code | 118 blank | 5 comment | 0 complexity | 33e5203a87a8872d02d87c9604540166 MD5 | raw file
  1. require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
  2. class MachineCollectionByDefaultTest < Test::Unit::TestCase
  3. def setup
  4. @machines = StateMachine::MachineCollection.new
  5. end
  6. def test_should_not_have_any_machines
  7. assert @machines.empty?
  8. end
  9. end
  10. class MachineCollectionStateInitializationTest < Test::Unit::TestCase
  11. def setup
  12. @machines = StateMachine::MachineCollection.new
  13. @klass = Class.new
  14. @machines[:state] = StateMachine::Machine.new(@klass, :state, :initial => :parked)
  15. @machines[:alarm_state] = StateMachine::Machine.new(@klass, :alarm_state, :initial => lambda {|object| :active})
  16. @machines[:alarm_state].state :active, :value => lambda {'active'}
  17. # Prevent the auto-initialization hook from firing
  18. @klass.class_eval do
  19. def initialize
  20. end
  21. end
  22. @object = @klass.new
  23. @object.state = nil
  24. @object.alarm_state = nil
  25. end
  26. def test_should_set_states_if_nil
  27. @machines.initialize_states(@object)
  28. assert_equal 'parked', @object.state
  29. assert_equal 'active', @object.alarm_state
  30. end
  31. def test_should_set_states_if_empty
  32. @object.state = ''
  33. @object.alarm_state = ''
  34. @machines.initialize_states(@object)
  35. assert_equal 'parked', @object.state
  36. assert_equal 'active', @object.alarm_state
  37. end
  38. def test_should_not_set_states_if_not_empty
  39. @object.state = 'idling'
  40. @object.alarm_state = 'off'
  41. @machines.initialize_states(@object)
  42. assert_equal 'idling', @object.state
  43. assert_equal 'off', @object.alarm_state
  44. end
  45. def test_should_only_initialize_static_states_if_dynamic_disabled
  46. @machines.initialize_states(@object, :dynamic => false)
  47. assert_equal 'parked', @object.state
  48. assert_nil @object.alarm_state
  49. end
  50. def test_should_only_initialize_dynamic_states_if_dynamic_enabled
  51. @machines.initialize_states(@object, :dynamic => true)
  52. assert_nil @object.state
  53. assert_equal 'active', @object.alarm_state
  54. end
  55. def test_should_not_set_states_if_ignored
  56. @machines.initialize_states(@object, :ignore => [:state, :alarm_state])
  57. assert_nil @object.state
  58. assert_nil @object.alarm_state
  59. end
  60. def test_should_set_states_if_not_ignored_and_nil
  61. @machines.initialize_states(@object, :ignore => [])
  62. assert_equal 'parked', @object.state
  63. assert_equal 'active', @object.alarm_state
  64. end
  65. def test_should_set_states_if_not_ignored_and_empty
  66. @object.state = ''
  67. @object.alarm_state = ''
  68. @machines.initialize_states(@object, :ignore => [])
  69. assert_equal 'parked', @object.state
  70. assert_equal 'active', @object.alarm_state
  71. end
  72. def test_should_set_states_if_not_ignored_and_not_empty
  73. @object.state = 'idling'
  74. @object.alarm_state = 'inactive'
  75. @machines.initialize_states(@object, :ignore => [])
  76. assert_equal 'parked', @object.state
  77. assert_equal 'active', @object.alarm_state
  78. end
  79. def test_should_not_modify_ignore_option
  80. ignore = ['state', 'alarm_state']
  81. @machines.initialize_states(@object, :ignore => ignore)
  82. assert_nil @object.state
  83. assert_nil @object.alarm_state
  84. assert_equal ['state', 'alarm_state'], ignore
  85. end
  86. end
  87. class MachineCollectionFireTest < Test::Unit::TestCase
  88. def setup
  89. @machines = StateMachine::MachineCollection.new
  90. @klass = Class.new do
  91. attr_reader :saved
  92. def save
  93. @saved = true
  94. end
  95. end
  96. # First machine
  97. @machines[:state] = @state = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
  98. @state.event :ignite do
  99. transition :parked => :idling
  100. end
  101. @state.event :park do
  102. transition :idling => :parked
  103. end
  104. # Second machine
  105. @machines[:alarm_state] = @alarm_state = StateMachine::Machine.new(@klass, :alarm_state, :initial => :active, :action => :save, :namespace => 'alarm')
  106. @alarm_state.event :enable do
  107. transition :off => :active
  108. end
  109. @alarm_state.event :disable do
  110. transition :active => :off
  111. end
  112. @object = @klass.new
  113. end
  114. def test_should_raise_exception_if_invalid_event_specified
  115. exception = assert_raise(StateMachine::InvalidEvent) { @machines.fire_events(@object, :invalid) }
  116. assert_equal ':invalid is an unknown state machine event', exception.message
  117. exception = assert_raise(StateMachine::InvalidEvent) { @machines.fire_events(@object, :ignite, :invalid) }
  118. assert_equal ':invalid is an unknown state machine event', exception.message
  119. end
  120. def test_should_fail_if_any_event_cannot_transition
  121. assert !@machines.fire_events(@object, :park, :disable_alarm)
  122. assert_equal 'parked', @object.state
  123. assert_equal 'active', @object.alarm_state
  124. assert !@object.saved
  125. assert !@machines.fire_events(@object, :ignite, :enable_alarm)
  126. assert_equal 'parked', @object.state
  127. assert_equal 'active', @object.alarm_state
  128. assert !@object.saved
  129. end
  130. def test_should_be_successful_if_all_events_transition
  131. assert @machines.fire_events(@object, :ignite, :disable_alarm)
  132. assert_equal 'idling', @object.state
  133. assert_equal 'off', @object.alarm_state
  134. assert @object.saved
  135. end
  136. def test_should_not_save_if_skipping_action
  137. assert @machines.fire_events(@object, :ignite, :disable_alarm, false)
  138. assert_equal 'idling', @object.state
  139. assert_equal 'off', @object.alarm_state
  140. assert !@object.saved
  141. end
  142. end
  143. class MachineCollectionFireWithTransactionsTest < Test::Unit::TestCase
  144. def setup
  145. @machines = StateMachine::MachineCollection.new
  146. @klass = Class.new do
  147. attr_accessor :allow_save
  148. def save
  149. @allow_save
  150. end
  151. end
  152. StateMachine::Integrations.const_set('Custom', Module.new do
  153. attr_reader :rolled_back
  154. def transaction(object)
  155. @rolled_back = yield
  156. end
  157. end)
  158. # First machine
  159. @machines[:state] = @state = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save, :integration => :custom)
  160. @state.event :ignite do
  161. transition :parked => :idling
  162. end
  163. # Second machine
  164. @machines[:alarm_state] = @alarm_state = StateMachine::Machine.new(@klass, :alarm_state, :initial => :active, :action => :save, :namespace => 'alarm', :integration => :custom)
  165. @alarm_state.event :disable do
  166. transition :active => :off
  167. end
  168. @object = @klass.new
  169. end
  170. def test_should_not_rollback_if_successful
  171. @object.allow_save = true
  172. assert @machines.fire_events(@object, :ignite, :disable_alarm)
  173. assert_equal true, @state.rolled_back
  174. assert_nil @alarm_state.rolled_back
  175. assert_equal 'idling', @object.state
  176. assert_equal 'off', @object.alarm_state
  177. end
  178. def test_should_rollback_if_not_successful
  179. @object.allow_save = false
  180. assert !@machines.fire_events(@object, :ignite, :disable_alarm)
  181. assert_equal false, @state.rolled_back
  182. assert_nil @alarm_state.rolled_back
  183. assert_equal 'parked', @object.state
  184. assert_equal 'active', @object.alarm_state
  185. end
  186. def teardown
  187. StateMachine::Integrations.send(:remove_const, 'Custom')
  188. end
  189. end
  190. class MachineCollectionFireWithValidationsTest < Test::Unit::TestCase
  191. def setup
  192. StateMachine::Integrations.const_set('Custom', Module.new do
  193. def invalidate(object, attribute, message, values = [])
  194. (object.errors ||= []) << generate_message(message, values)
  195. end
  196. def reset(object)
  197. object.errors = []
  198. end
  199. end)
  200. @klass = Class.new do
  201. attr_accessor :errors
  202. def initialize
  203. @errors = []
  204. super
  205. end
  206. end
  207. @machines = StateMachine::MachineCollection.new
  208. @machines[:state] = @state = StateMachine::Machine.new(@klass, :state, :initial => :parked, :integration => :custom)
  209. @state.event :ignite do
  210. transition :parked => :idling
  211. end
  212. @machines[:alarm_state] = @alarm_state = StateMachine::Machine.new(@klass, :alarm_state, :initial => :active, :namespace => 'alarm', :integration => :custom)
  213. @alarm_state.event :disable do
  214. transition :active => :off
  215. end
  216. @object = @klass.new
  217. end
  218. def test_should_not_invalidate_if_transitions_exist
  219. assert @machines.fire_events(@object, :ignite, :disable_alarm)
  220. assert_equal [], @object.errors
  221. end
  222. def test_should_invalidate_if_no_transitions_exist
  223. @object.state = 'idling'
  224. @object.alarm_state = 'off'
  225. assert !@machines.fire_events(@object, :ignite, :disable_alarm)
  226. assert_equal ['cannot transition via "ignite"', 'cannot transition via "disable"'], @object.errors
  227. end
  228. def teardown
  229. StateMachine::Integrations.send(:remove_const, 'Custom')
  230. end
  231. end
  232. class MachineCollectionTransitionsWithoutEventsTest < Test::Unit::TestCase
  233. def setup
  234. @klass = Class.new
  235. @machines = StateMachine::MachineCollection.new
  236. @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
  237. @machine.event :ignite do
  238. transition :parked => :idling
  239. end
  240. @object = @klass.new
  241. @object.state_event = nil
  242. @transitions = @machines.transitions(@object, :save)
  243. end
  244. def test_should_be_empty
  245. assert @transitions.empty?
  246. end
  247. def test_should_perform
  248. assert_equal true, @transitions.perform
  249. end
  250. end
  251. class MachineCollectionTransitionsWithBlankEventsTest < Test::Unit::TestCase
  252. def setup
  253. @klass = Class.new
  254. @machines = StateMachine::MachineCollection.new
  255. @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
  256. @machine.event :ignite do
  257. transition :parked => :idling
  258. end
  259. @object = @klass.new
  260. @object.state_event = ''
  261. @transitions = @machines.transitions(@object, :save)
  262. end
  263. def test_should_be_empty
  264. assert @transitions.empty?
  265. end
  266. def test_should_perform
  267. assert_equal true, @transitions.perform
  268. end
  269. end
  270. class MachineCollectionTransitionsWithInvalidEventsTest < Test::Unit::TestCase
  271. def setup
  272. @klass = Class.new
  273. @machines = StateMachine::MachineCollection.new
  274. @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
  275. @machine.event :ignite do
  276. transition :parked => :idling
  277. end
  278. @object = @klass.new
  279. @object.state_event = 'invalid'
  280. @transitions = @machines.transitions(@object, :save)
  281. end
  282. def test_should_be_empty
  283. assert @transitions.empty?
  284. end
  285. def test_should_not_perform
  286. assert_equal false, @transitions.perform
  287. end
  288. end
  289. class MachineCollectionTransitionsWithoutTransitionTest < Test::Unit::TestCase
  290. def setup
  291. @klass = Class.new
  292. @machines = StateMachine::MachineCollection.new
  293. @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
  294. @machine.event :ignite do
  295. transition :parked => :idling
  296. end
  297. @object = @klass.new
  298. @object.state = 'idling'
  299. @object.state_event = 'ignite'
  300. @transitions = @machines.transitions(@object, :save)
  301. end
  302. def test_should_be_empty
  303. assert @transitions.empty?
  304. end
  305. def test_should_not_perform
  306. assert_equal false, @transitions.perform
  307. end
  308. end
  309. class MachineCollectionTransitionsWithTransitionTest < Test::Unit::TestCase
  310. def setup
  311. @klass = Class.new
  312. @machines = StateMachine::MachineCollection.new
  313. @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
  314. @machine.event :ignite do
  315. transition :parked => :idling
  316. end
  317. @object = @klass.new
  318. @object.state_event = 'ignite'
  319. @transitions = @machines.transitions(@object, :save)
  320. end
  321. def test_should_not_be_empty
  322. assert_equal 1, @transitions.length
  323. end
  324. def test_should_perform
  325. assert_equal true, @transitions.perform
  326. end
  327. end
  328. class MachineCollectionTransitionsWithSameActionsTest < Test::Unit::TestCase
  329. def setup
  330. @klass = Class.new
  331. @machines = StateMachine::MachineCollection.new
  332. @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
  333. @machine.event :ignite do
  334. transition :parked => :idling
  335. end
  336. @machines[:status] = @machine = StateMachine::Machine.new(@klass, :status, :initial => :first_gear, :action => :save)
  337. @machine.event :shift_up do
  338. transition :first_gear => :second_gear
  339. end
  340. @object = @klass.new
  341. @object.state_event = 'ignite'
  342. @object.status_event = 'shift_up'
  343. @transitions = @machines.transitions(@object, :save)
  344. end
  345. def test_should_not_be_empty
  346. assert_equal 2, @transitions.length
  347. end
  348. def test_should_perform
  349. assert_equal true, @transitions.perform
  350. end
  351. end
  352. class MachineCollectionTransitionsWithDifferentActionsTest < Test::Unit::TestCase
  353. def setup
  354. @klass = Class.new
  355. @machines = StateMachine::MachineCollection.new
  356. @machines[:state] = @state = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
  357. @state.event :ignite do
  358. transition :parked => :idling
  359. end
  360. @machines[:status] = @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear, :action => :persist)
  361. @status.event :shift_up do
  362. transition :first_gear => :second_gear
  363. end
  364. @object = @klass.new
  365. @object.state_event = 'ignite'
  366. @object.status_event = 'shift_up'
  367. @transitions = @machines.transitions(@object, :save)
  368. end
  369. def test_should_only_select_matching_actions
  370. assert_equal 1, @transitions.length
  371. end
  372. end
  373. class MachineCollectionTransitionsWithExisitingTransitionsTest < Test::Unit::TestCase
  374. def setup
  375. @klass = Class.new
  376. @machines = StateMachine::MachineCollection.new
  377. @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
  378. @machine.event :ignite do
  379. transition :parked => :idling
  380. end
  381. @object = @klass.new
  382. @object.send(:state_event_transition=, StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling))
  383. @transitions = @machines.transitions(@object, :save)
  384. end
  385. def test_should_not_be_empty
  386. assert_equal 1, @transitions.length
  387. end
  388. def test_should_perform
  389. assert_equal true, @transitions.perform
  390. end
  391. end
  392. class MachineCollectionTransitionsWithCustomOptionsTest < Test::Unit::TestCase
  393. def setup
  394. @klass = Class.new
  395. @machines = StateMachine::MachineCollection.new
  396. @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
  397. @machine.event :ignite do
  398. transition :parked => :idling
  399. end
  400. @object = @klass.new
  401. @transitions = @machines.transitions(@object, :save, :after => false)
  402. end
  403. def test_should_use_custom_options
  404. assert @transitions.skip_after
  405. end
  406. end
  407. class MachineCollectionFireAttributesWithValidationsTest < Test::Unit::TestCase
  408. def setup
  409. @klass = Class.new do
  410. attr_accessor :errors
  411. def initialize
  412. @errors = []
  413. super
  414. end
  415. end
  416. @machines = StateMachine::MachineCollection.new
  417. @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
  418. @machine.event :ignite do
  419. transition :parked => :idling
  420. end
  421. class << @machine
  422. def invalidate(object, attribute, message, values = [])
  423. (object.errors ||= []) << generate_message(message, values)
  424. end
  425. def reset(object)
  426. object.errors = []
  427. end
  428. end
  429. @object = @klass.new
  430. end
  431. def test_should_invalidate_if_event_is_invalid
  432. @object.state_event = 'invalid'
  433. @machines.transitions(@object, :save)
  434. assert !@object.errors.empty?
  435. end
  436. def test_should_invalidate_if_no_transition_exists
  437. @object.state = 'idling'
  438. @object.state_event = 'ignite'
  439. @machines.transitions(@object, :save)
  440. assert !@object.errors.empty?
  441. end
  442. def test_should_not_invalidate_if_transition_exists
  443. @object.state_event = 'ignite'
  444. @machines.transitions(@object, :save)
  445. assert @object.errors.empty?
  446. end
  447. end