PageRenderTime 37ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 0ms

/test/unit/integrations/active_model_test.rb

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