PageRenderTime 46ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/test/unit/integrations/active_model_test.rb

https://github.com/johnsonzes/state_machine
Ruby | 1097 lines | 856 code | 231 blank | 10 comment | 3 complexity | f623e60784af5639de6b668929e8a42c MD5 | raw file
  1. require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
  2. # Load library
  3. require 'rubygems'
  4. gem 'activemodel', ENV['VERSION'] ? "=#{ENV['VERSION']}" : '>=3.0.0.beta'
  5. require 'active_model'
  6. require 'active_model/observing'
  7. require 'active_support/all'
  8. module ActiveModelTest
  9. class BaseTestCase < Test::Unit::TestCase
  10. def default_test
  11. end
  12. protected
  13. # Creates a new ActiveModel model (and the associated table)
  14. def new_model(&block)
  15. # Simple ActiveModel superclass
  16. parent = Class.new do
  17. def self.model_attribute(name)
  18. define_method(name) { instance_variable_get("@#{name}") }
  19. define_method("#{name}=") do |value|
  20. send("#{name}_will_change!") if self.class <= ActiveModel::Dirty && !send("#{name}_changed?")
  21. instance_variable_set("@#{name}", value)
  22. end
  23. end
  24. def self.create
  25. object = new
  26. object.save
  27. object
  28. end
  29. def initialize(attrs = {})
  30. attrs.each {|attr, value| send("#{attr}=", value)}
  31. @changed_attributes = {}
  32. end
  33. def attributes
  34. @attributes ||= {}
  35. end
  36. def save
  37. @changed_attributes = {}
  38. true
  39. end
  40. end
  41. model = Class.new(parent) do
  42. def self.name
  43. 'ActiveModelTest::Foo'
  44. end
  45. model_attribute :state
  46. end
  47. model.class_eval(&block) if block_given?
  48. model
  49. end
  50. # Creates a new ActiveModel observer
  51. def new_observer(model, &block)
  52. observer = Class.new(ActiveModel::Observer) do
  53. attr_accessor :notifications
  54. def initialize
  55. super
  56. @notifications = []
  57. end
  58. end
  59. observer.observe(model)
  60. observer.class_eval(&block) if block_given?
  61. observer
  62. end
  63. end
  64. class IntegrationTest < BaseTestCase
  65. def test_should_have_an_integration_name
  66. assert_equal :active_model, StateMachine::Integrations::ActiveModel.integration_name
  67. end
  68. def test_should_be_available
  69. assert StateMachine::Integrations::ActiveModel.available?
  70. end
  71. def test_should_match_if_class_includes_dirty_feature
  72. assert StateMachine::Integrations::ActiveModel.matches?(new_model { include ActiveModel::Dirty })
  73. end
  74. def test_should_match_if_class_includes_observing_feature
  75. assert StateMachine::Integrations::ActiveModel.matches?(new_model { include ActiveModel::Observing })
  76. end
  77. def test_should_match_if_class_includes_validations_feature
  78. assert StateMachine::Integrations::ActiveModel.matches?(new_model { include ActiveModel::Validations })
  79. end
  80. def test_should_not_match_if_class_does_not_include_active_model_features
  81. assert !StateMachine::Integrations::ActiveModel.matches?(new_model)
  82. end
  83. def test_should_have_no_defaults
  84. assert_equal e = {}, StateMachine::Integrations::ActiveModel.defaults
  85. end
  86. def test_should_have_a_locale_path
  87. assert_not_nil StateMachine::Integrations::ActiveModel.locale_path
  88. end
  89. end
  90. class MachineByDefaultTest < BaseTestCase
  91. def setup
  92. @model = new_model
  93. @machine = StateMachine::Machine.new(@model, :integration => :active_model)
  94. end
  95. def test_should_not_have_action
  96. assert_nil @machine.action
  97. end
  98. def test_should_use_transactions
  99. assert_equal true, @machine.use_transactions
  100. end
  101. def test_should_not_have_any_before_callbacks
  102. assert_equal 0, @machine.callbacks[:before].size
  103. end
  104. def test_should_not_have_any_after_callbacks
  105. assert_equal 0, @machine.callbacks[:after].size
  106. end
  107. end
  108. class MachineWithStatesTest < BaseTestCase
  109. def setup
  110. @model = new_model
  111. @machine = StateMachine::Machine.new(@model)
  112. @machine.state :first_gear
  113. end
  114. def test_should_humanize_name
  115. assert_equal 'first gear', @machine.state(:first_gear).human_name
  116. end
  117. end
  118. class MachineWithStaticInitialStateTest < BaseTestCase
  119. def setup
  120. @model = new_model
  121. @machine = StateMachine::Machine.new(@model, :initial => :parked, :integration => :active_model)
  122. end
  123. def test_should_set_initial_state_on_created_object
  124. record = @model.new
  125. assert_equal 'parked', record.state
  126. end
  127. end
  128. class MachineWithDynamicInitialStateTest < BaseTestCase
  129. def setup
  130. @model = new_model
  131. @machine = StateMachine::Machine.new(@model, :initial => lambda {|object| :parked}, :integration => :active_model)
  132. @machine.state :parked
  133. end
  134. def test_should_set_initial_state_on_created_object
  135. record = @model.new
  136. assert_equal 'parked', record.state
  137. end
  138. end
  139. class MachineWithEventsTest < BaseTestCase
  140. def setup
  141. @model = new_model
  142. @machine = StateMachine::Machine.new(@model)
  143. @machine.event :shift_up
  144. end
  145. def test_should_humanize_name
  146. assert_equal 'shift up', @machine.event(:shift_up).human_name
  147. end
  148. end
  149. class MachineWithModelStateAttributeTest < BaseTestCase
  150. def setup
  151. @model = new_model
  152. @machine = StateMachine::Machine.new(@model, :initial => :parked, :integration => :active_model)
  153. @machine.other_states(:idling)
  154. @record = @model.new
  155. end
  156. def test_should_have_an_attribute_predicate
  157. assert @record.respond_to?(:state?)
  158. end
  159. def test_should_raise_exception_for_predicate_without_parameters
  160. exception = assert_raise(ArgumentError) { @record.state? }
  161. assert_equal 'wrong number of arguments (1 for 2)', exception.message
  162. end
  163. def test_should_return_false_for_predicate_if_does_not_match_current_value
  164. assert !@record.state?(:idling)
  165. end
  166. def test_should_return_true_for_predicate_if_matches_current_value
  167. assert @record.state?(:parked)
  168. end
  169. def test_should_raise_exception_for_predicate_if_invalid_state_specified
  170. assert_raise(IndexError) { @record.state?(:invalid) }
  171. end
  172. end
  173. class MachineWithNonModelStateAttributeUndefinedTest < BaseTestCase
  174. def setup
  175. @model = new_model do
  176. def initialize
  177. end
  178. end
  179. @machine = StateMachine::Machine.new(@model, :status, :initial => :parked, :integration => :active_model)
  180. @machine.other_states(:idling)
  181. @record = @model.new
  182. end
  183. def test_should_not_define_a_reader_attribute_for_the_attribute
  184. assert !@record.respond_to?(:status)
  185. end
  186. def test_should_not_define_a_writer_attribute_for_the_attribute
  187. assert !@record.respond_to?(:status=)
  188. end
  189. def test_should_define_an_attribute_predicate
  190. assert @record.respond_to?(:status?)
  191. end
  192. end
  193. class MachineWithInitializedStateTest < BaseTestCase
  194. def setup
  195. @model = new_model
  196. @machine = StateMachine::Machine.new(@model, :initial => :parked, :integration => :active_model)
  197. @machine.state nil, :idling
  198. end
  199. def test_should_allow_nil_initial_state_when_static
  200. @machine.state nil
  201. record = @model.new(:state => nil)
  202. assert_nil record.state
  203. end
  204. def test_should_allow_nil_initial_state_when_dynamic
  205. @machine.state nil
  206. @machine.initial_state = lambda {:parked}
  207. record = @model.new(:state => nil)
  208. assert_nil record.state
  209. end
  210. def test_should_allow_different_initial_state_when_static
  211. record = @model.new(:state => 'idling')
  212. assert_equal 'idling', record.state
  213. end
  214. def test_should_allow_different_initial_state_when_dynamic
  215. @machine.initial_state = lambda {:parked}
  216. record = @model.new(:state => 'idling')
  217. assert_equal 'idling', record.state
  218. end
  219. def test_should_use_default_state_if_protected
  220. @model.class_eval do
  221. include ActiveModel::MassAssignmentSecurity
  222. attr_protected :state
  223. def initialize(attrs = {})
  224. initialize_state_machines do
  225. sanitize_for_mass_assignment(attrs).each {|attr, value| send("#{attr}=", value)} if attrs
  226. @changed_attributes = {}
  227. end
  228. end
  229. end
  230. record = @model.new(:state => 'idling')
  231. assert_equal 'parked', record.state
  232. record = @model.new(nil)
  233. assert_equal 'parked', record.state
  234. end
  235. end
  236. class MachineMultipleTest < BaseTestCase
  237. def setup
  238. @model = new_model do
  239. model_attribute :status
  240. end
  241. @state_machine = StateMachine::Machine.new(@model, :initial => :parked, :integration => :active_model)
  242. @status_machine = StateMachine::Machine.new(@model, :status, :initial => :idling, :integration => :active_model)
  243. end
  244. def test_should_should_initialize_each_state
  245. record = @model.new
  246. assert_equal 'parked', record.state
  247. assert_equal 'idling', record.status
  248. end
  249. end
  250. class MachineWithDirtyAttributesTest < BaseTestCase
  251. def setup
  252. @model = new_model do
  253. include ActiveModel::Dirty
  254. define_attribute_methods [:state]
  255. end
  256. @machine = StateMachine::Machine.new(@model, :initial => :parked)
  257. @machine.event :ignite
  258. @machine.state :idling
  259. @record = @model.create
  260. @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
  261. @transition.perform
  262. end
  263. def test_should_include_state_in_changed_attributes
  264. assert_equal %w(state), @record.changed
  265. end
  266. def test_should_track_attribute_change
  267. assert_equal %w(parked idling), @record.changes['state']
  268. end
  269. def test_should_not_reset_changes_on_multiple_transitions
  270. transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling)
  271. transition.perform
  272. assert_equal %w(parked idling), @record.changes['state']
  273. end
  274. end
  275. class MachineWithDirtyAttributesDuringLoopbackTest < BaseTestCase
  276. def setup
  277. @model = new_model do
  278. include ActiveModel::Dirty
  279. define_attribute_methods [:state]
  280. end
  281. @machine = StateMachine::Machine.new(@model, :initial => :parked)
  282. @machine.event :park
  283. @record = @model.create
  284. @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
  285. @transition.perform
  286. end
  287. def test_should_include_state_in_changed_attributes
  288. assert_equal %w(state), @record.changed
  289. end
  290. def test_should_track_attribute_changes
  291. assert_equal %w(parked parked), @record.changes['state']
  292. end
  293. end
  294. class MachineWithDirtyAttributesAndCustomAttributeTest < BaseTestCase
  295. def setup
  296. @model = new_model do
  297. include ActiveModel::Dirty
  298. model_attribute :status
  299. define_attribute_methods [:status]
  300. end
  301. @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
  302. @machine.event :ignite
  303. @machine.state :idling
  304. @record = @model.create
  305. @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
  306. @transition.perform
  307. end
  308. def test_should_include_state_in_changed_attributes
  309. assert_equal %w(status), @record.changed
  310. end
  311. def test_should_track_attribute_change
  312. assert_equal %w(parked idling), @record.changes['status']
  313. end
  314. def test_should_not_reset_changes_on_multiple_transitions
  315. transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling)
  316. transition.perform
  317. assert_equal %w(parked idling), @record.changes['status']
  318. end
  319. end
  320. class MachineWithDirtyAttributeAndCustomAttributesDuringLoopbackTest < BaseTestCase
  321. def setup
  322. @model = new_model do
  323. include ActiveModel::Dirty
  324. model_attribute :status
  325. define_attribute_methods [:status]
  326. end
  327. @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
  328. @machine.event :park
  329. @record = @model.create
  330. @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
  331. @transition.perform
  332. end
  333. def test_should_include_state_in_changed_attributes
  334. assert_equal %w(status), @record.changed
  335. end
  336. def test_should_track_attribute_changes
  337. assert_equal %w(parked parked), @record.changes['status']
  338. end
  339. end
  340. class MachineWithDirtyAttributeAndStateEventsTest < BaseTestCase
  341. def setup
  342. @model = new_model do
  343. include ActiveModel::Dirty
  344. define_attribute_methods [:state]
  345. end
  346. @machine = StateMachine::Machine.new(@model, :action => :save, :initial => :parked)
  347. @machine.event :ignite
  348. @record = @model.create
  349. @record.state_event = 'ignite'
  350. end
  351. def test_should_include_state_in_changed_attributes
  352. assert_equal %w(state), @record.changed
  353. end
  354. def test_should_track_attribute_change
  355. assert_equal %w(parked parked), @record.changes['state']
  356. end
  357. def test_should_not_reset_changes_on_multiple_changes
  358. @record.state_event = 'ignite'
  359. assert_equal %w(parked parked), @record.changes['state']
  360. end
  361. def test_should_not_include_state_in_changed_attributes_if_nil
  362. @record = @model.create
  363. @record.state_event = nil
  364. assert_equal [], @record.changed
  365. end
  366. end
  367. class MachineWithCallbacksTest < BaseTestCase
  368. def setup
  369. @model = new_model
  370. @machine = StateMachine::Machine.new(@model, :initial => :parked, :integration => :active_model)
  371. @machine.other_states :idling
  372. @machine.event :ignite
  373. @record = @model.new(:state => 'parked')
  374. @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
  375. end
  376. def test_should_run_before_callbacks
  377. called = false
  378. @machine.before_transition {called = true}
  379. @transition.perform
  380. assert called
  381. end
  382. def test_should_pass_record_to_before_callbacks_with_one_argument
  383. record = nil
  384. @machine.before_transition {|arg| record = arg}
  385. @transition.perform
  386. assert_equal @record, record
  387. end
  388. def test_should_pass_record_and_transition_to_before_callbacks_with_multiple_arguments
  389. callback_args = nil
  390. @machine.before_transition {|*args| callback_args = args}
  391. @transition.perform
  392. assert_equal [@record, @transition], callback_args
  393. end
  394. def test_should_run_before_callbacks_outside_the_context_of_the_record
  395. context = nil
  396. @machine.before_transition {context = self}
  397. @transition.perform
  398. assert_equal self, context
  399. end
  400. def test_should_run_after_callbacks
  401. called = false
  402. @machine.after_transition {called = true}
  403. @transition.perform
  404. assert called
  405. end
  406. def test_should_pass_record_to_after_callbacks_with_one_argument
  407. record = nil
  408. @machine.after_transition {|arg| record = arg}
  409. @transition.perform
  410. assert_equal @record, record
  411. end
  412. def test_should_pass_record_and_transition_to_after_callbacks_with_multiple_arguments
  413. callback_args = nil
  414. @machine.after_transition {|*args| callback_args = args}
  415. @transition.perform
  416. assert_equal [@record, @transition], callback_args
  417. end
  418. def test_should_run_after_callbacks_outside_the_context_of_the_record
  419. context = nil
  420. @machine.after_transition {context = self}
  421. @transition.perform
  422. assert_equal self, context
  423. end
  424. def test_should_run_around_callbacks
  425. before_called = false
  426. after_called = false
  427. @machine.around_transition {|block| before_called = true; block.call; after_called = true}
  428. @transition.perform
  429. assert before_called
  430. assert after_called
  431. end
  432. def test_should_include_transition_states_in_known_states
  433. @machine.before_transition :to => :first_gear, :do => lambda {}
  434. assert_equal [:parked, :idling, :first_gear], @machine.states.map {|state| state.name}
  435. end
  436. def test_should_allow_symbolic_callbacks
  437. callback_args = nil
  438. klass = class << @record; self; end
  439. klass.send(:define_method, :after_ignite) do |*args|
  440. callback_args = args
  441. end
  442. @machine.before_transition(:after_ignite)
  443. @transition.perform
  444. assert_equal [@transition], callback_args
  445. end
  446. def test_should_allow_string_callbacks
  447. class << @record
  448. attr_reader :callback_result
  449. end
  450. @machine.before_transition('@callback_result = [1, 2, 3]')
  451. @transition.perform
  452. assert_equal [1, 2, 3], @record.callback_result
  453. end
  454. end
  455. class MachineWithFailedBeforeCallbacksTest < BaseTestCase
  456. def setup
  457. @callbacks = []
  458. @model = new_model
  459. @machine = StateMachine::Machine.new(@model, :integration => :active_model)
  460. @machine.state :parked, :idling
  461. @machine.event :ignite
  462. @machine.before_transition {@callbacks << :before_1; false}
  463. @machine.before_transition {@callbacks << :before_2}
  464. @machine.after_transition {@callbacks << :after}
  465. @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after}
  466. @record = @model.new(:state => 'parked')
  467. @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
  468. @result = @transition.perform
  469. end
  470. def test_should_not_be_successful
  471. assert !@result
  472. end
  473. def test_should_not_change_current_state
  474. assert_equal 'parked', @record.state
  475. end
  476. def test_should_not_run_further_callbacks
  477. assert_equal [:before_1], @callbacks
  478. end
  479. end
  480. class MachineWithFailedAfterCallbacksTest < BaseTestCase
  481. def setup
  482. @callbacks = []
  483. @model = new_model
  484. @machine = StateMachine::Machine.new(@model, :integration => :active_model)
  485. @machine.state :parked, :idling
  486. @machine.event :ignite
  487. @machine.after_transition {@callbacks << :after_1; false}
  488. @machine.after_transition {@callbacks << :after_2}
  489. @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after}
  490. @record = @model.new(:state => 'parked')
  491. @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
  492. @result = @transition.perform
  493. end
  494. def test_should_be_successful
  495. assert @result
  496. end
  497. def test_should_change_current_state
  498. assert_equal 'idling', @record.state
  499. end
  500. def test_should_not_run_further_after_callbacks
  501. assert_equal [:around_before, :around_after, :after_1], @callbacks
  502. end
  503. end
  504. class MachineWithValidationsTest < BaseTestCase
  505. def setup
  506. @model = new_model { include ActiveModel::Validations }
  507. @machine = StateMachine::Machine.new(@model, :action => :save)
  508. @machine.state :parked
  509. @record = @model.new
  510. end
  511. def test_should_invalidate_using_errors
  512. I18n.backend = I18n::Backend::Simple.new if Object.const_defined?(:I18n)
  513. @record.state = 'parked'
  514. @machine.invalidate(@record, :state, :invalid_transition, [[:event, 'park']])
  515. assert_equal ['State cannot transition via "park"'], @record.errors.full_messages
  516. end
  517. def test_should_auto_prefix_custom_attributes_on_invalidation
  518. @machine.invalidate(@record, :event, :invalid)
  519. assert_equal ['State event is invalid'], @record.errors.full_messages
  520. end
  521. def test_should_clear_errors_on_reset
  522. @record.state = 'parked'
  523. @record.errors.add(:state, 'is invalid')
  524. @machine.reset(@record)
  525. assert_equal [], @record.errors.full_messages
  526. end
  527. def test_should_be_valid_if_state_is_known
  528. @record.state = 'parked'
  529. assert @record.valid?
  530. end
  531. def test_should_not_be_valid_if_state_is_unknown
  532. @record.state = 'invalid'
  533. assert !@record.valid?
  534. assert_equal ['State is invalid'], @record.errors.full_messages
  535. end
  536. end
  537. class MachineWithValidationsAndCustomAttributeTest < BaseTestCase
  538. def setup
  539. @model = new_model { include ActiveModel::Validations }
  540. @machine = StateMachine::Machine.new(@model, :status, :attribute => :state)
  541. @machine.state :parked
  542. @record = @model.new
  543. end
  544. def test_should_add_validation_errors_to_custom_attribute
  545. @record.state = 'invalid'
  546. assert !@record.valid?
  547. assert_equal ['State is invalid'], @record.errors.full_messages
  548. @record.state = 'parked'
  549. assert @record.valid?
  550. end
  551. end
  552. class MachineWithStateDrivenValidationsTest < BaseTestCase
  553. def setup
  554. @model = new_model do
  555. include ActiveModel::Validations
  556. attr_accessor :seatbelt
  557. end
  558. @machine = StateMachine::Machine.new(@model)
  559. @machine.state :first_gear, :second_gear do
  560. validates_presence_of :seatbelt
  561. end
  562. @machine.other_states :parked
  563. end
  564. def test_should_be_valid_if_validation_fails_outside_state_scope
  565. record = @model.new(:state => 'parked', :seatbelt => nil)
  566. assert record.valid?
  567. end
  568. def test_should_be_invalid_if_validation_fails_within_state_scope
  569. record = @model.new(:state => 'first_gear', :seatbelt => nil)
  570. assert !record.valid?
  571. end
  572. def test_should_be_valid_if_validation_succeeds_within_state_scope
  573. record = @model.new(:state => 'second_gear', :seatbelt => true)
  574. assert record.valid?
  575. end
  576. end
  577. class MachineWithObserversTest < BaseTestCase
  578. def setup
  579. @model = new_model { include ActiveModel::Observing }
  580. @machine = StateMachine::Machine.new(@model)
  581. @machine.state :parked, :idling
  582. @machine.event :ignite
  583. @record = @model.new(:state => 'parked')
  584. @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
  585. end
  586. def test_should_call_all_transition_callback_permutations
  587. callbacks = [
  588. :before_ignite_from_parked_to_idling,
  589. :before_ignite_from_parked,
  590. :before_ignite_to_idling,
  591. :before_ignite,
  592. :before_transition_state_from_parked_to_idling,
  593. :before_transition_state_from_parked,
  594. :before_transition_state_to_idling,
  595. :before_transition_state,
  596. :before_transition
  597. ]
  598. notified = false
  599. observer = new_observer(@model) do
  600. callbacks.each do |callback|
  601. define_method(callback) do |*args|
  602. notifications << callback
  603. end
  604. end
  605. end
  606. instance = observer.instance
  607. @transition.perform
  608. assert_equal callbacks, instance.notifications
  609. end
  610. def test_should_pass_record_and_transition_to_before_callbacks
  611. observer = new_observer(@model) do
  612. def before_transition(*args)
  613. notifications << args
  614. end
  615. end
  616. instance = observer.instance
  617. @transition.perform
  618. assert_equal [[@record, @transition]], instance.notifications
  619. end
  620. def test_should_pass_record_and_transition_to_after_callbacks
  621. observer = new_observer(@model) do
  622. def after_transition(*args)
  623. notifications << args
  624. end
  625. end
  626. instance = observer.instance
  627. @transition.perform
  628. assert_equal [[@record, @transition]], instance.notifications
  629. end
  630. def test_should_call_methods_outside_the_context_of_the_record
  631. observer = new_observer(@model) do
  632. def before_ignite(*args)
  633. notifications << self
  634. end
  635. end
  636. instance = observer.instance
  637. @transition.perform
  638. assert_equal [instance], instance.notifications
  639. end
  640. end
  641. class MachineWithNamespacedObserversTest < BaseTestCase
  642. def setup
  643. @model = new_model { include ActiveModel::Observing }
  644. @machine = StateMachine::Machine.new(@model, :state, :namespace => 'alarm')
  645. @machine.state :active, :off
  646. @machine.event :enable
  647. @record = @model.new(:state => 'off')
  648. @transition = StateMachine::Transition.new(@record, @machine, :enable, :off, :active)
  649. end
  650. def test_should_call_namespaced_before_event_method
  651. observer = new_observer(@model) do
  652. def before_enable_alarm(*args)
  653. notifications << args
  654. end
  655. end
  656. instance = observer.instance
  657. @transition.perform
  658. assert_equal [[@record, @transition]], instance.notifications
  659. end
  660. def test_should_call_namespaced_after_event_method
  661. observer = new_observer(@model) do
  662. def after_enable_alarm(*args)
  663. notifications << args
  664. end
  665. end
  666. instance = observer.instance
  667. @transition.perform
  668. assert_equal [[@record, @transition]], instance.notifications
  669. end
  670. end
  671. class MachineWithFailureCallbacksTest < BaseTestCase
  672. def setup
  673. @model = new_model { include ActiveModel::Observing }
  674. @machine = StateMachine::Machine.new(@model)
  675. @machine.state :parked, :idling
  676. @machine.event :ignite
  677. @record = @model.new(:state => 'parked')
  678. @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
  679. @notifications = []
  680. # Create callbacks
  681. @machine.before_transition {false}
  682. @machine.after_failure {@notifications << :callback_after_failure}
  683. # Create observer callbacks
  684. observer = new_observer(@model) do
  685. def after_failure_to_ignite(*args)
  686. notifications << :observer_after_failure_ignite
  687. end
  688. def after_failure_to_transition(*args)
  689. notifications << :observer_after_failure_transition
  690. end
  691. end
  692. instance = observer.instance
  693. instance.notifications = @notifications
  694. @transition.perform
  695. end
  696. def test_should_invoke_callbacks_in_specific_order
  697. expected = [
  698. :callback_after_failure,
  699. :observer_after_failure_ignite,
  700. :observer_after_failure_transition
  701. ]
  702. assert_equal expected, @notifications
  703. end
  704. end
  705. class MachineWithMixedCallbacksTest < BaseTestCase
  706. def setup
  707. @model = new_model { include ActiveModel::Observing }
  708. @machine = StateMachine::Machine.new(@model)
  709. @machine.state :parked, :idling
  710. @machine.event :ignite
  711. @record = @model.new(:state => 'parked')
  712. @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
  713. @notifications = []
  714. # Create callbacks
  715. @machine.before_transition {@notifications << :callback_before_transition}
  716. @machine.after_transition {@notifications << :callback_after_transition}
  717. @machine.around_transition {|block| @notifications << :callback_around_before_transition; block.call; @notifications << :callback_around_after_transition}
  718. # Create observer callbacks
  719. observer = new_observer(@model) do
  720. def before_ignite(*args)
  721. notifications << :observer_before_ignite
  722. end
  723. def before_transition(*args)
  724. notifications << :observer_before_transition
  725. end
  726. def after_ignite(*args)
  727. notifications << :observer_after_ignite
  728. end
  729. def after_transition(*args)
  730. notifications << :observer_after_transition
  731. end
  732. end
  733. instance = observer.instance
  734. instance.notifications = @notifications
  735. @transition.perform
  736. end
  737. def test_should_invoke_callbacks_in_specific_order
  738. expected = [
  739. :callback_before_transition,
  740. :callback_around_before_transition,
  741. :observer_before_ignite,
  742. :observer_before_transition,
  743. :callback_around_after_transition,
  744. :callback_after_transition,
  745. :observer_after_ignite,
  746. :observer_after_transition
  747. ]
  748. assert_equal expected, @notifications
  749. end
  750. end
  751. class MachineWithInternationalizationTest < BaseTestCase
  752. def setup
  753. I18n.backend = I18n::Backend::Simple.new
  754. # Initialize the backend
  755. I18n.backend.translate(:en, 'activemodel.errors.messages.invalid_transition', :event => 'ignite', :value => 'idling')
  756. @model = new_model { include ActiveModel::Validations }
  757. end
  758. def test_should_use_defaults
  759. I18n.backend.store_translations(:en, {
  760. :activemodel => {:errors => {:messages => {:invalid_transition => 'cannot %{event}'}}}
  761. })
  762. machine = StateMachine::Machine.new(@model, :action => :save)
  763. machine.state :parked, :idling
  764. machine.event :ignite
  765. record = @model.new(:state => 'idling')
  766. machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
  767. assert_equal ['State cannot ignite'], record.errors.full_messages
  768. end
  769. def test_should_allow_customized_error_key
  770. I18n.backend.store_translations(:en, {
  771. :activemodel => {:errors => {:messages => {:bad_transition => 'cannot %{event}'}}}
  772. })
  773. machine = StateMachine::Machine.new(@model, :action => :save, :messages => {:invalid_transition => :bad_transition})
  774. machine.state :parked, :idling
  775. record = @model.new
  776. record.state = 'idling'
  777. machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
  778. assert_equal ['State cannot ignite'], record.errors.full_messages
  779. end
  780. def test_should_allow_customized_error_string
  781. machine = StateMachine::Machine.new(@model, :action => :save, :messages => {:invalid_transition => 'cannot %{event}'})
  782. machine.state :parked, :idling
  783. record = @model.new(:state => 'idling')
  784. machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
  785. assert_equal ['State cannot ignite'], record.errors.full_messages
  786. end
  787. def test_should_allow_customized_state_key_scoped_to_class_and_machine
  788. I18n.backend.store_translations(:en, {
  789. :activemodel => {:state_machines => {:'active_model_test/foo' => {:state => {:states => {:parked => 'shutdown'}}}}}
  790. })
  791. machine = StateMachine::Machine.new(@model)
  792. machine.state :parked
  793. assert_equal 'shutdown', machine.state(:parked).human_name
  794. end
  795. def test_should_allow_customized_state_key_scoped_to_machine
  796. I18n.backend.store_translations(:en, {
  797. :activemodel => {:state_machines => {:state => {:states => {:parked => 'shutdown'}}}}
  798. })
  799. machine = StateMachine::Machine.new(@model)
  800. machine.state :parked
  801. assert_equal 'shutdown', machine.state(:parked).human_name
  802. end
  803. def test_should_allow_customized_state_key_unscoped
  804. I18n.backend.store_translations(:en, {
  805. :activemodel => {:state_machines => {:states => {:parked => 'shutdown'}}}
  806. })
  807. machine = StateMachine::Machine.new(@model)
  808. machine.state :parked
  809. assert_equal 'shutdown', machine.state(:parked).human_name
  810. end
  811. def test_should_allow_customized_event_key_scoped_to_class_and_machine
  812. I18n.backend.store_translations(:en, {
  813. :activemodel => {:state_machines => {:'active_model_test/foo' => {:state => {:events => {:park => 'stop'}}}}}
  814. })
  815. machine = StateMachine::Machine.new(@model)
  816. machine.event :park
  817. assert_equal 'stop', machine.event(:park).human_name
  818. end
  819. def test_should_allow_customized_event_key_scoped_to_machine
  820. I18n.backend.store_translations(:en, {
  821. :activemodel => {:state_machines => {:state => {:events => {:park => 'stop'}}}}
  822. })
  823. machine = StateMachine::Machine.new(@model)
  824. machine.event :park
  825. assert_equal 'stop', machine.event(:park).human_name
  826. end
  827. def test_should_allow_customized_event_key_unscoped
  828. I18n.backend.store_translations(:en, {
  829. :activemodel => {:state_machines => {:events => {:park => 'stop'}}}
  830. })
  831. machine = StateMachine::Machine.new(@model)
  832. machine.event :park
  833. assert_equal 'stop', machine.event(:park).human_name
  834. end
  835. def test_should_only_add_locale_once_in_load_path
  836. assert_equal 1, I18n.load_path.select {|path| path =~ %r{active_model/locale\.rb$}}.length
  837. # Create another ActiveModel model that will triger the i18n feature
  838. new_model
  839. assert_equal 1, I18n.load_path.select {|path| path =~ %r{active_model/locale\.rb$}}.length
  840. end
  841. def test_should_add_locale_to_beginning_of_load_path
  842. @original_load_path = I18n.load_path
  843. I18n.backend = I18n::Backend::Simple.new
  844. app_locale = File.dirname(__FILE__) + '/../../files/en.yml'
  845. default_locale = File.dirname(__FILE__) + '/../../../lib/state_machine/integrations/active_model/locale.rb'
  846. I18n.load_path = [app_locale]
  847. StateMachine::Machine.new(@model)
  848. assert_equal [default_locale, app_locale].map {|path| File.expand_path(path)}, I18n.load_path.map {|path| File.expand_path(path)}
  849. ensure
  850. I18n.load_path = @original_load_path
  851. end
  852. def test_should_prefer_other_locales_first
  853. @original_load_path = I18n.load_path
  854. I18n.backend = I18n::Backend::Simple.new
  855. I18n.load_path = [File.dirname(__FILE__) + '/../../files/en.yml']
  856. machine = StateMachine::Machine.new(@model)
  857. machine.state :parked, :idling
  858. machine.event :ignite
  859. record = @model.new(:state => 'idling')
  860. machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
  861. assert_equal ['State cannot ignite'], record.errors.full_messages
  862. ensure
  863. I18n.load_path = @original_load_path
  864. end
  865. end
  866. end