state_machines 0.5.0 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (486) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE.txt +1 -1
  3. data/README.md +443 -22
  4. data/lib/state_machines/async_mode/async_event_extensions.rb +49 -0
  5. data/lib/state_machines/async_mode/async_events.rb +282 -0
  6. data/lib/state_machines/async_mode/async_machine.rb +60 -0
  7. data/lib/state_machines/async_mode/async_transition_collection.rb +141 -0
  8. data/lib/state_machines/async_mode/thread_safe_state.rb +47 -0
  9. data/lib/state_machines/async_mode.rb +64 -0
  10. data/lib/state_machines/branch.rb +146 -86
  11. data/lib/state_machines/callback.rb +35 -32
  12. data/lib/state_machines/core.rb +3 -3
  13. data/lib/state_machines/core_ext/class/state_machine.rb +2 -0
  14. data/lib/state_machines/core_ext.rb +2 -0
  15. data/lib/state_machines/error.rb +7 -4
  16. data/lib/state_machines/eval_helpers.rb +197 -39
  17. data/lib/state_machines/event.rb +77 -58
  18. data/lib/state_machines/event_collection.rb +49 -39
  19. data/lib/state_machines/extensions.rb +6 -4
  20. data/lib/state_machines/helper_module.rb +4 -2
  21. data/lib/state_machines/integrations/base.rb +3 -1
  22. data/lib/state_machines/integrations.rb +19 -20
  23. data/lib/state_machines/machine/action_hooks.rb +53 -0
  24. data/lib/state_machines/machine/async_extensions.rb +88 -0
  25. data/lib/state_machines/machine/callbacks.rb +59 -0
  26. data/lib/state_machines/machine/class_methods.rb +97 -0
  27. data/lib/state_machines/machine/configuration.rb +134 -0
  28. data/lib/state_machines/machine/event_methods.rb +59 -0
  29. data/lib/state_machines/machine/helper_generators.rb +125 -0
  30. data/lib/state_machines/machine/integration.rb +70 -0
  31. data/lib/state_machines/machine/parsing.rb +77 -0
  32. data/lib/state_machines/machine/rendering.rb +17 -0
  33. data/lib/state_machines/machine/scoping.rb +44 -0
  34. data/lib/state_machines/machine/state_methods.rb +101 -0
  35. data/lib/state_machines/machine/utilities.rb +85 -0
  36. data/lib/state_machines/machine/validation.rb +39 -0
  37. data/lib/state_machines/machine.rb +425 -1011
  38. data/lib/state_machines/machine_collection.rb +28 -19
  39. data/lib/state_machines/macro_methods.rb +104 -102
  40. data/lib/state_machines/matcher.rb +31 -28
  41. data/lib/state_machines/matcher_helpers.rb +14 -12
  42. data/lib/state_machines/node_collection.rb +36 -29
  43. data/lib/state_machines/options_validator.rb +72 -0
  44. data/lib/state_machines/path.rb +60 -57
  45. data/lib/state_machines/path_collection.rb +39 -36
  46. data/lib/state_machines/state.rb +84 -47
  47. data/lib/state_machines/state_collection.rb +22 -19
  48. data/lib/state_machines/state_context.rb +40 -39
  49. data/lib/state_machines/stdio_renderer.rb +74 -0
  50. data/lib/state_machines/syntax_validator.rb +57 -0
  51. data/lib/state_machines/test_helper.rb +896 -0
  52. data/lib/state_machines/transition.rb +215 -199
  53. data/lib/state_machines/transition_collection.rb +187 -170
  54. data/lib/state_machines/version.rb +3 -1
  55. data/lib/state_machines.rb +4 -1
  56. metadata +39 -446
  57. data/.gitignore +0 -21
  58. data/.rspec +0 -3
  59. data/.ruby-gemset +0 -1
  60. data/.ruby-version +0 -1
  61. data/.travis.yml +0 -16
  62. data/Changelog.md +0 -22
  63. data/Contributors.md +0 -39
  64. data/Gemfile +0 -8
  65. data/Rakefile +0 -12
  66. data/Testing.md +0 -0
  67. data/lib/state_machines/assertions.rb +0 -40
  68. data/state_machines.gemspec +0 -22
  69. data/test/files/integrations/event_on_failure_integration.rb +0 -10
  70. data/test/files/integrations/vehicle.rb +0 -7
  71. data/test/files/models/auto_shop.rb +0 -31
  72. data/test/files/models/car.rb +0 -21
  73. data/test/files/models/driver.rb +0 -13
  74. data/test/files/models/model_base.rb +0 -6
  75. data/test/files/models/motorcycle.rb +0 -16
  76. data/test/files/models/traffic_light.rb +0 -47
  77. data/test/files/models/vehicle.rb +0 -127
  78. data/test/files/node.rb +0 -5
  79. data/test/files/switch.rb +0 -15
  80. data/test/functional/auto_shop_available_test.rb +0 -20
  81. data/test/functional/auto_shop_busy_test.rb +0 -25
  82. data/test/functional/car_backing_up_test.rb +0 -45
  83. data/test/functional/car_test.rb +0 -49
  84. data/test/functional/driver_default_nonstandard_test.rb +0 -13
  85. data/test/functional/motorcycle_test.rb +0 -52
  86. data/test/functional/traffic_light_caution_test.rb +0 -17
  87. data/test/functional/traffic_light_proceed_test.rb +0 -17
  88. data/test/functional/traffic_light_stop_test.rb +0 -26
  89. data/test/functional/vehicle_first_gear_test.rb +0 -42
  90. data/test/functional/vehicle_idling_test.rb +0 -59
  91. data/test/functional/vehicle_locked_test.rb +0 -29
  92. data/test/functional/vehicle_parked_test.rb +0 -53
  93. data/test/functional/vehicle_repaired_test.rb +0 -20
  94. data/test/functional/vehicle_second_gear_test.rb +0 -42
  95. data/test/functional/vehicle_stalled_test.rb +0 -65
  96. data/test/functional/vehicle_test.rb +0 -20
  97. data/test/functional/vehicle_third_gear_test.rb +0 -42
  98. data/test/functional/vehicle_unsaved_test.rb +0 -181
  99. data/test/functional/vehicle_with_event_attributes_test.rb +0 -30
  100. data/test/functional/vehicle_with_parallel_events_test.rb +0 -36
  101. data/test/test_helper.rb +0 -15
  102. data/test/unit/assertions/assert_exclusive_keys_test.rb +0 -22
  103. data/test/unit/assertions/assert_valid_key_test.rb +0 -12
  104. data/test/unit/branch/branch_test.rb +0 -28
  105. data/test/unit/branch/branch_with_conflicting_conditionals_test.rb +0 -27
  106. data/test/unit/branch/branch_with_conflicting_from_requirements_test.rb +0 -8
  107. data/test/unit/branch/branch_with_conflicting_on_requirements_test.rb +0 -8
  108. data/test/unit/branch/branch_with_conflicting_to_requirements_test.rb +0 -8
  109. data/test/unit/branch/branch_with_different_requirements_test.rb +0 -41
  110. data/test/unit/branch/branch_with_except_from_matcher_requirement_test.rb +0 -8
  111. data/test/unit/branch/branch_with_except_from_requirement_test.rb +0 -36
  112. data/test/unit/branch/branch_with_except_on_matcher_requirement_test.rb +0 -8
  113. data/test/unit/branch/branch_with_except_on_requirement_test.rb +0 -36
  114. data/test/unit/branch/branch_with_except_to_matcher_requirement_test.rb +0 -8
  115. data/test/unit/branch/branch_with_except_to_requirement_test.rb +0 -36
  116. data/test/unit/branch/branch_with_from_matcher_requirement_test.rb +0 -20
  117. data/test/unit/branch/branch_with_from_requirement_test.rb +0 -45
  118. data/test/unit/branch/branch_with_if_conditional_test.rb +0 -27
  119. data/test/unit/branch/branch_with_implicit_and_explicit_requirements_test.rb +0 -23
  120. data/test/unit/branch/branch_with_implicit_from_requirement_matcher_test.rb +0 -20
  121. data/test/unit/branch/branch_with_implicit_requirement_test.rb +0 -20
  122. data/test/unit/branch/branch_with_implicit_to_requirement_matcher_test.rb +0 -16
  123. data/test/unit/branch/branch_with_multiple_except_from_requirements_test.rb +0 -20
  124. data/test/unit/branch/branch_with_multiple_except_on_requirements_test.rb +0 -16
  125. data/test/unit/branch/branch_with_multiple_except_to_requirements_test.rb +0 -20
  126. data/test/unit/branch/branch_with_multiple_from_requirements_test.rb +0 -16
  127. data/test/unit/branch/branch_with_multiple_if_conditionals_test.rb +0 -20
  128. data/test/unit/branch/branch_with_multiple_implicit_requirements_test.rb +0 -53
  129. data/test/unit/branch/branch_with_multiple_to_requirements_test.rb +0 -20
  130. data/test/unit/branch/branch_with_multiple_unless_conditionals_test.rb +0 -20
  131. data/test/unit/branch/branch_with_nil_requirements_test.rb +0 -28
  132. data/test/unit/branch/branch_with_no_requirements_test.rb +0 -36
  133. data/test/unit/branch/branch_with_on_matcher_requirement_test.rb +0 -16
  134. data/test/unit/branch/branch_with_on_requirement_test.rb +0 -45
  135. data/test/unit/branch/branch_with_to_matcher_requirement_test.rb +0 -20
  136. data/test/unit/branch/branch_with_to_requirement_test.rb +0 -45
  137. data/test/unit/branch/branch_with_unless_conditional_test.rb +0 -27
  138. data/test/unit/branch/branch_without_guards_test.rb +0 -27
  139. data/test/unit/callback/callback_by_default_test.rb +0 -25
  140. data/test/unit/callback/callback_test.rb +0 -53
  141. data/test/unit/callback/callback_with_application_bound_object_test.rb +0 -23
  142. data/test/unit/callback/callback_with_application_terminator_test.rb +0 -24
  143. data/test/unit/callback/callback_with_arguments_test.rb +0 -14
  144. data/test/unit/callback/callback_with_around_type_and_arguments_test.rb +0 -25
  145. data/test/unit/callback/callback_with_around_type_and_block_test.rb +0 -44
  146. data/test/unit/callback/callback_with_around_type_and_bound_method_test.rb +0 -23
  147. data/test/unit/callback/callback_with_around_type_and_multiple_methods_test.rb +0 -93
  148. data/test/unit/callback/callback_with_around_type_and_terminator_test.rb +0 -17
  149. data/test/unit/callback/callback_with_block_test.rb +0 -20
  150. data/test/unit/callback/callback_with_bound_method_and_arguments_test.rb +0 -28
  151. data/test/unit/callback/callback_with_bound_method_test.rb +0 -35
  152. data/test/unit/callback/callback_with_do_method_test.rb +0 -18
  153. data/test/unit/callback/callback_with_explicit_requirements_test.rb +0 -32
  154. data/test/unit/callback/callback_with_if_condition_test.rb +0 -17
  155. data/test/unit/callback/callback_with_implicit_requirements_test.rb +0 -32
  156. data/test/unit/callback/callback_with_method_argument_test.rb +0 -18
  157. data/test/unit/callback/callback_with_mixed_methods_test.rb +0 -31
  158. data/test/unit/callback/callback_with_multiple_bound_methods_test.rb +0 -21
  159. data/test/unit/callback/callback_with_multiple_do_methods_test.rb +0 -29
  160. data/test/unit/callback/callback_with_multiple_method_arguments_test.rb +0 -29
  161. data/test/unit/callback/callback_with_terminator_test.rb +0 -22
  162. data/test/unit/callback/callback_with_unbound_method_test.rb +0 -14
  163. data/test/unit/callback/callback_with_unless_condition_test.rb +0 -17
  164. data/test/unit/callback/callback_without_arguments_test.rb +0 -14
  165. data/test/unit/callback/callback_without_terminator_test.rb +0 -12
  166. data/test/unit/error/error_by_default_test.rb +0 -21
  167. data/test/unit/error/error_with_message_test.rb +0 -23
  168. data/test/unit/eval_helper/eval_helpers_base_test.rb +0 -8
  169. data/test/unit/eval_helper/eval_helpers_proc_block_and_explicit_arguments_test.rb +0 -14
  170. data/test/unit/eval_helper/eval_helpers_proc_block_and_implicit_arguments_test.rb +0 -14
  171. data/test/unit/eval_helper/eval_helpers_proc_test.rb +0 -13
  172. data/test/unit/eval_helper/eval_helpers_proc_with_arguments_test.rb +0 -13
  173. data/test/unit/eval_helper/eval_helpers_proc_with_block_test.rb +0 -13
  174. data/test/unit/eval_helper/eval_helpers_proc_with_block_without_arguments_test.rb +0 -18
  175. data/test/unit/eval_helper/eval_helpers_proc_with_block_without_object_test.rb +0 -14
  176. data/test/unit/eval_helper/eval_helpers_proc_without_arguments_test.rb +0 -19
  177. data/test/unit/eval_helper/eval_helpers_string_test.rb +0 -25
  178. data/test/unit/eval_helper/eval_helpers_string_with_block_test.rb +0 -12
  179. data/test/unit/eval_helper/eval_helpers_symbol_method_missing_test.rb +0 -20
  180. data/test/unit/eval_helper/eval_helpers_symbol_private_test.rb +0 -17
  181. data/test/unit/eval_helper/eval_helpers_symbol_protected_test.rb +0 -17
  182. data/test/unit/eval_helper/eval_helpers_symbol_tainted_method_test.rb +0 -18
  183. data/test/unit/eval_helper/eval_helpers_symbol_test.rb +0 -16
  184. data/test/unit/eval_helper/eval_helpers_symbol_with_arguments_and_block_test.rb +0 -16
  185. data/test/unit/eval_helper/eval_helpers_symbol_with_arguments_test.rb +0 -16
  186. data/test/unit/eval_helper/eval_helpers_symbol_with_block_test.rb +0 -16
  187. data/test/unit/eval_helper/eval_helpers_test.rb +0 -13
  188. data/test/unit/event/event_after_being_copied_test.rb +0 -17
  189. data/test/unit/event/event_by_default_test.rb +0 -60
  190. data/test/unit/event/event_context_test.rb +0 -16
  191. data/test/unit/event/event_on_failure_test.rb +0 -44
  192. data/test/unit/event/event_test.rb +0 -34
  193. data/test/unit/event/event_transitions_test.rb +0 -62
  194. data/test/unit/event/event_with_conflicting_helpers_after_definition_test.rb +0 -79
  195. data/test/unit/event/event_with_conflicting_helpers_before_definition_test.rb +0 -58
  196. data/test/unit/event/event_with_conflicting_machine_test.rb +0 -48
  197. data/test/unit/event/event_with_dynamic_human_name_test.rb +0 -26
  198. data/test/unit/event/event_with_human_name_test.rb +0 -13
  199. data/test/unit/event/event_with_invalid_current_state_test.rb +0 -30
  200. data/test/unit/event/event_with_machine_action_test.rb +0 -33
  201. data/test/unit/event/event_with_marshalling_test.rb +0 -47
  202. data/test/unit/event/event_with_matching_disabled_transitions_test.rb +0 -115
  203. data/test/unit/event/event_with_matching_enabled_transitions_test.rb +0 -75
  204. data/test/unit/event/event_with_multiple_transitions_test.rb +0 -61
  205. data/test/unit/event/event_with_namespace_test.rb +0 -34
  206. data/test/unit/event/event_with_transition_with_blacklisted_to_state_test.rb +0 -60
  207. data/test/unit/event/event_with_transition_with_loopback_state_test.rb +0 -36
  208. data/test/unit/event/event_with_transition_with_nil_to_state_test.rb +0 -36
  209. data/test/unit/event/event_with_transition_with_whitelisted_to_state_test.rb +0 -51
  210. data/test/unit/event/event_with_transition_without_to_state_test.rb +0 -36
  211. data/test/unit/event/event_with_transitions_test.rb +0 -32
  212. data/test/unit/event/event_without_matching_transitions_test.rb +0 -41
  213. data/test/unit/event/event_without_transitions_test.rb +0 -28
  214. data/test/unit/event/invalid_event_test.rb +0 -20
  215. data/test/unit/event_collection/event_collection_attribute_with_machine_action_test.rb +0 -62
  216. data/test/unit/event_collection/event_collection_attribute_with_namespaced_machine_test.rb +0 -36
  217. data/test/unit/event_collection/event_collection_by_default_test.rb +0 -26
  218. data/test/unit/event_collection/event_collection_test.rb +0 -39
  219. data/test/unit/event_collection/event_collection_with_custom_machine_attribute_test.rb +0 -31
  220. data/test/unit/event_collection/event_collection_with_events_with_transitions_test.rb +0 -76
  221. data/test/unit/event_collection/event_collection_with_multiple_events_test.rb +0 -27
  222. data/test/unit/event_collection/event_collection_with_validations_test.rb +0 -74
  223. data/test/unit/event_collection/event_collection_without_machine_action_test.rb +0 -18
  224. data/test/unit/event_collection/event_string_collection_test.rb +0 -31
  225. data/test/unit/helper_module_test.rb +0 -17
  226. data/test/unit/integrations/integration_finder_test.rb +0 -16
  227. data/test/unit/integrations/integration_matcher_test.rb +0 -29
  228. data/test/unit/invalid_transition/invalid_parallel_transition_test.rb +0 -18
  229. data/test/unit/invalid_transition/invalid_transition_test.rb +0 -47
  230. data/test/unit/invalid_transition/invalid_transition_with_integration_test.rb +0 -45
  231. data/test/unit/invalid_transition/invalid_transition_with_namespace_test.rb +0 -32
  232. data/test/unit/machine/machine_after_being_copied_test.rb +0 -62
  233. data/test/unit/machine/machine_after_changing_initial_state.rb +0 -28
  234. data/test/unit/machine/machine_after_changing_owner_class_test.rb +0 -31
  235. data/test/unit/machine/machine_by_default_test.rb +0 -160
  236. data/test/unit/machine/machine_finder_custom_options_test.rb +0 -17
  237. data/test/unit/machine/machine_finder_with_existing_machine_on_superclass_test.rb +0 -85
  238. data/test/unit/machine/machine_finder_with_existing_on_same_class_test.rb +0 -23
  239. data/test/unit/machine/machine_finder_without_existing_machine_test.rb +0 -25
  240. data/test/unit/machine/machine_persistence_test.rb +0 -52
  241. data/test/unit/machine/machine_state_initialization_test.rb +0 -56
  242. data/test/unit/machine/machine_test.rb +0 -30
  243. data/test/unit/machine/machine_with_action_already_overridden_test.rb +0 -23
  244. data/test/unit/machine/machine_with_action_defined_in_class_test.rb +0 -37
  245. data/test/unit/machine/machine_with_action_defined_in_included_module_test.rb +0 -46
  246. data/test/unit/machine/machine_with_action_defined_in_superclass_test.rb +0 -43
  247. data/test/unit/machine/machine_with_action_undefined_test.rb +0 -33
  248. data/test/unit/machine/machine_with_cached_state_test.rb +0 -20
  249. data/test/unit/machine/machine_with_class_helpers_test.rb +0 -179
  250. data/test/unit/machine/machine_with_conflicting_helpers_after_definition_test.rb +0 -244
  251. data/test/unit/machine/machine_with_conflicting_helpers_before_definition_test.rb +0 -175
  252. data/test/unit/machine/machine_with_custom_action_test.rb +0 -11
  253. data/test/unit/machine/machine_with_custom_attribute_test.rb +0 -103
  254. data/test/unit/machine/machine_with_custom_initialize_test.rb +0 -24
  255. data/test/unit/machine/machine_with_custom_integration_test.rb +0 -72
  256. data/test/unit/machine/machine_with_custom_invalidation_test.rb +0 -39
  257. data/test/unit/machine/machine_with_custom_name_test.rb +0 -57
  258. data/test/unit/machine/machine_with_custom_plural_test.rb +0 -52
  259. data/test/unit/machine/machine_with_dynamic_initial_state_test.rb +0 -65
  260. data/test/unit/machine/machine_with_event_matchers_test.rb +0 -41
  261. data/test/unit/machine/machine_with_events_test.rb +0 -52
  262. data/test/unit/machine/machine_with_events_with_custom_human_names_test.rb +0 -18
  263. data/test/unit/machine/machine_with_events_with_transitions_test.rb +0 -37
  264. data/test/unit/machine/machine_with_existing_event_test.rb +0 -17
  265. data/test/unit/machine/machine_with_existing_machines_on_owner_class_test.rb +0 -20
  266. data/test/unit/machine/machine_with_existing_machines_with_same_attributes_on_owner_class_test.rb +0 -71
  267. data/test/unit/machine/machine_with_existing_machines_with_same_attributes_on_owner_subclass_test.rb +0 -31
  268. data/test/unit/machine/machine_with_existing_state_test.rb +0 -27
  269. data/test/unit/machine/machine_with_failure_callbacks_test.rb +0 -48
  270. data/test/unit/machine/machine_with_helpers_test.rb +0 -14
  271. data/test/unit/machine/machine_with_initial_state_with_value_and_owner_default.rb +0 -25
  272. data/test/unit/machine/machine_with_initialize_and_super_test.rb +0 -17
  273. data/test/unit/machine/machine_with_initialize_arguments_and_block_test.rb +0 -31
  274. data/test/unit/machine/machine_with_initialize_without_super_test.rb +0 -17
  275. data/test/unit/machine/machine_with_instance_helpers_test.rb +0 -179
  276. data/test/unit/machine/machine_with_integration_test.rb +0 -72
  277. data/test/unit/machine/machine_with_multiple_events_test.rb +0 -32
  278. data/test/unit/machine/machine_with_namespace_test.rb +0 -48
  279. data/test/unit/machine/machine_with_nil_action_test.rb +0 -27
  280. data/test/unit/machine/machine_with_other_states.rb +0 -22
  281. data/test/unit/machine/machine_with_owner_subclass_test.rb +0 -18
  282. data/test/unit/machine/machine_with_paths_test.rb +0 -25
  283. data/test/unit/machine/machine_with_private_action_test.rb +0 -43
  284. data/test/unit/machine/machine_with_state_matchers_test.rb +0 -41
  285. data/test/unit/machine/machine_with_state_with_matchers_test.rb +0 -19
  286. data/test/unit/machine/machine_with_states_test.rb +0 -55
  287. data/test/unit/machine/machine_with_states_with_behaviors_test.rb +0 -23
  288. data/test/unit/machine/machine_with_states_with_custom_human_names_test.rb +0 -18
  289. data/test/unit/machine/machine_with_states_with_custom_values_test.rb +0 -21
  290. data/test/unit/machine/machine_with_states_with_runtime_dependencies_test.rb +0 -19
  291. data/test/unit/machine/machine_with_static_initial_state_test.rb +0 -49
  292. data/test/unit/machine/machine_with_superclass_conflicting_helpers_after_definition_test.rb +0 -36
  293. data/test/unit/machine/machine_with_transition_callbacks_test.rb +0 -144
  294. data/test/unit/machine/machine_with_transitions_test.rb +0 -87
  295. data/test/unit/machine/machine_without_initialization_test.rb +0 -31
  296. data/test/unit/machine/machine_without_initialize_test.rb +0 -14
  297. data/test/unit/machine/machine_without_integration_test.rb +0 -31
  298. data/test/unit/machine_collection/machine_collection_by_default_test.rb +0 -11
  299. data/test/unit/machine_collection/machine_collection_fire_test.rb +0 -80
  300. data/test/unit/machine_collection/machine_collection_fire_with_transactions_test.rb +0 -54
  301. data/test/unit/machine_collection/machine_collection_fire_with_validations_test.rb +0 -76
  302. data/test/unit/machine_collection/machine_collection_state_initialization_test.rb +0 -111
  303. data/test/unit/machine_collection/machine_collection_transitions_with_blank_events_test.rb +0 -25
  304. data/test/unit/machine_collection/machine_collection_transitions_with_custom_options_test.rb +0 -20
  305. data/test/unit/machine_collection/machine_collection_transitions_with_different_actions_test.rb +0 -26
  306. data/test/unit/machine_collection/machine_collection_transitions_with_exisiting_transitions_test.rb +0 -25
  307. data/test/unit/machine_collection/machine_collection_transitions_with_invalid_events_test.rb +0 -25
  308. data/test/unit/machine_collection/machine_collection_transitions_with_same_actions_test.rb +0 -31
  309. data/test/unit/machine_collection/machine_collection_transitions_with_transition_test.rb +0 -26
  310. data/test/unit/machine_collection/machine_collection_transitions_without_events_test.rb +0 -25
  311. data/test/unit/machine_collection/machine_collection_transitions_without_transition_test.rb +0 -27
  312. data/test/unit/matcher/all_matcher_test.rb +0 -29
  313. data/test/unit/matcher/blacklist_matcher_test.rb +0 -30
  314. data/test/unit/matcher/loopback_matcher_test.rb +0 -27
  315. data/test/unit/matcher/matcher_by_default_test.rb +0 -15
  316. data/test/unit/matcher/matcher_with_multiple_values_test.rb +0 -15
  317. data/test/unit/matcher/matcher_with_value_test.rb +0 -15
  318. data/test/unit/matcher/whitelist_matcher_test.rb +0 -30
  319. data/test/unit/matcher_helpers/matcher_helpers_all_test.rb +0 -14
  320. data/test/unit/matcher_helpers/matcher_helpers_any_test.rb +0 -14
  321. data/test/unit/matcher_helpers/matcher_helpers_same_test.rb +0 -13
  322. data/test/unit/node_collection/node_collection_after_being_copied_test.rb +0 -46
  323. data/test/unit/node_collection/node_collection_after_update_test.rb +0 -36
  324. data/test/unit/node_collection/node_collection_by_default_test.rb +0 -22
  325. data/test/unit/node_collection/node_collection_test.rb +0 -23
  326. data/test/unit/node_collection/node_collection_with_indices_test.rb +0 -42
  327. data/test/unit/node_collection/node_collection_with_matcher_contexts_test.rb +0 -25
  328. data/test/unit/node_collection/node_collection_with_nodes_test.rb +0 -46
  329. data/test/unit/node_collection/node_collection_with_numeric_index_test.rb +0 -24
  330. data/test/unit/node_collection/node_collection_with_postdefined_contexts_test.rb +0 -22
  331. data/test/unit/node_collection/node_collection_with_predefined_contexts_test.rb +0 -23
  332. data/test/unit/node_collection/node_collection_with_string_index_test.rb +0 -20
  333. data/test/unit/node_collection/node_collection_with_symbol_index_test.rb +0 -20
  334. data/test/unit/node_collection/node_collection_without_indices_test.rb +0 -30
  335. data/test/unit/path/path_by_default_test.rb +0 -54
  336. data/test/unit/path/path_test.rb +0 -14
  337. data/test/unit/path/path_with_available_transitions_after_reaching_target_test.rb +0 -40
  338. data/test/unit/path/path_with_available_transitions_test.rb +0 -54
  339. data/test/unit/path/path_with_deep_target_reached_test.rb +0 -50
  340. data/test/unit/path/path_with_deep_target_test.rb +0 -40
  341. data/test/unit/path/path_with_duplicates_test.rb +0 -32
  342. data/test/unit/path/path_with_encountered_transitions_test.rb +0 -34
  343. data/test/unit/path/path_with_guarded_transitions_test.rb +0 -42
  344. data/test/unit/path/path_with_reached_target_test.rb +0 -35
  345. data/test/unit/path/path_with_transitions_test.rb +0 -54
  346. data/test/unit/path/path_with_unreached_target_test.rb +0 -31
  347. data/test/unit/path/path_without_transitions_test.rb +0 -24
  348. data/test/unit/path_collection/path_collection_by_default_test.rb +0 -46
  349. data/test/unit/path_collection/path_collection_test.rb +0 -24
  350. data/test/unit/path_collection/path_collection_with_deep_paths_test.rb +0 -43
  351. data/test/unit/path_collection/path_collection_with_duplicate_nodes_test.rb +0 -31
  352. data/test/unit/path_collection/path_collection_with_from_state_test.rb +0 -27
  353. data/test/unit/path_collection/path_collection_with_paths_test.rb +0 -47
  354. data/test/unit/path_collection/path_collection_with_to_state_test.rb +0 -29
  355. data/test/unit/path_collection/path_with_guarded_paths_test.rb +0 -25
  356. data/test/unit/state/state_after_being_copied_test.rb +0 -19
  357. data/test/unit/state/state_by_default_test.rb +0 -41
  358. data/test/unit/state/state_final_test.rb +0 -28
  359. data/test/unit/state/state_initial_test.rb +0 -13
  360. data/test/unit/state/state_not_final_test.rb +0 -32
  361. data/test/unit/state/state_not_initial_test.rb +0 -13
  362. data/test/unit/state/state_test.rb +0 -44
  363. data/test/unit/state/state_with_cached_lambda_value_test.rb +0 -29
  364. data/test/unit/state/state_with_conflicting_helpers_after_definition_test.rb +0 -38
  365. data/test/unit/state/state_with_conflicting_helpers_before_definition_test.rb +0 -29
  366. data/test/unit/state/state_with_conflicting_machine_name_test.rb +0 -20
  367. data/test/unit/state/state_with_conflicting_machine_test.rb +0 -37
  368. data/test/unit/state/state_with_context_test.rb +0 -60
  369. data/test/unit/state/state_with_dynamic_human_name_test.rb +0 -25
  370. data/test/unit/state/state_with_existing_context_method_test.rb +0 -24
  371. data/test/unit/state/state_with_human_name_test.rb +0 -13
  372. data/test/unit/state/state_with_integer_value_test.rb +0 -32
  373. data/test/unit/state/state_with_invalid_method_call_test.rb +0 -21
  374. data/test/unit/state/state_with_lambda_value_test.rb +0 -37
  375. data/test/unit/state/state_with_matcher_test.rb +0 -18
  376. data/test/unit/state/state_with_multiple_contexts_test.rb +0 -57
  377. data/test/unit/state/state_with_name_test.rb +0 -43
  378. data/test/unit/state/state_with_namespace_test.rb +0 -22
  379. data/test/unit/state/state_with_nil_value_test.rb +0 -35
  380. data/test/unit/state/state_with_redefined_context_method_test.rb +0 -45
  381. data/test/unit/state/state_with_symbolic_value_test.rb +0 -32
  382. data/test/unit/state/state_with_valid_inherited_method_call_for_current_state_test.rb +0 -40
  383. data/test/unit/state/state_with_valid_method_call_for_current_state_test.rb +0 -33
  384. data/test/unit/state/state_with_valid_method_call_for_different_state_test.rb +0 -41
  385. data/test/unit/state/state_without_cached_lambda_value_test.rb +0 -25
  386. data/test/unit/state/state_without_name_test.rb +0 -39
  387. data/test/unit/state_collection/state_collection_by_default_test.rb +0 -21
  388. data/test/unit/state_collection/state_collection_string_test.rb +0 -35
  389. data/test/unit/state_collection/state_collection_test.rb +0 -74
  390. data/test/unit/state_collection/state_collection_with_custom_state_values_test.rb +0 -29
  391. data/test/unit/state_collection/state_collection_with_event_transitions_test.rb +0 -39
  392. data/test/unit/state_collection/state_collection_with_initial_state_test.rb +0 -40
  393. data/test/unit/state_collection/state_collection_with_namespace_test.rb +0 -21
  394. data/test/unit/state_collection/state_collection_with_state_behaviors_test.rb +0 -40
  395. data/test/unit/state_collection/state_collection_with_state_matchers_test.rb +0 -29
  396. data/test/unit/state_collection/state_collection_with_transition_callbacks_test.rb +0 -40
  397. data/test/unit/state_context/state_context_proxy_test.rb +0 -26
  398. data/test/unit/state_context/state_context_proxy_with_if_and_unless_conditions_test.rb +0 -42
  399. data/test/unit/state_context/state_context_proxy_with_if_condition_test.rb +0 -64
  400. data/test/unit/state_context/state_context_proxy_with_multiple_if_conditions_test.rb +0 -32
  401. data/test/unit/state_context/state_context_proxy_with_multiple_unless_conditions_test.rb +0 -32
  402. data/test/unit/state_context/state_context_proxy_with_unless_condition_test.rb +0 -64
  403. data/test/unit/state_context/state_context_proxy_without_conditions_test.rb +0 -31
  404. data/test/unit/state_context/state_context_test.rb +0 -28
  405. data/test/unit/state_context/state_context_transition_test.rb +0 -104
  406. data/test/unit/state_context/state_context_with_matching_transition_test.rb +0 -27
  407. data/test/unit/state_machine/state_machine_by_default_test.rb +0 -12
  408. data/test/unit/state_machine/state_machine_test.rb +0 -20
  409. data/test/unit/transition/transition_after_being_performed_test.rb +0 -48
  410. data/test/unit/transition/transition_after_being_persisted_test.rb +0 -46
  411. data/test/unit/transition/transition_after_being_rolled_back_test.rb +0 -35
  412. data/test/unit/transition/transition_equality_test.rb +0 -52
  413. data/test/unit/transition/transition_loopback_test.rb +0 -18
  414. data/test/unit/transition/transition_test.rb +0 -96
  415. data/test/unit/transition/transition_transient_test.rb +0 -20
  416. data/test/unit/transition/transition_with_action_test.rb +0 -27
  417. data/test/unit/transition/transition_with_after_callbacks_skipped_test.rb +0 -127
  418. data/test/unit/transition/transition_with_after_callbacks_test.rb +0 -93
  419. data/test/unit/transition/transition_with_around_callbacks_test.rb +0 -141
  420. data/test/unit/transition/transition_with_before_callbacks_skipped_test.rb +0 -30
  421. data/test/unit/transition/transition_with_before_callbacks_test.rb +0 -104
  422. data/test/unit/transition/transition_with_custom_machine_attribute_test.rb +0 -28
  423. data/test/unit/transition/transition_with_different_states_test.rb +0 -18
  424. data/test/unit/transition/transition_with_dynamic_to_value_test.rb +0 -19
  425. data/test/unit/transition/transition_with_failure_callbacks_test.rb +0 -84
  426. data/test/unit/transition/transition_with_invalid_nodes_test.rb +0 -29
  427. data/test/unit/transition/transition_with_mixed_callbacks_test.rb +0 -105
  428. data/test/unit/transition/transition_with_multiple_after_callbacks_test.rb +0 -40
  429. data/test/unit/transition/transition_with_multiple_around_callbacks_test.rb +0 -114
  430. data/test/unit/transition/transition_with_multiple_before_callbacks_test.rb +0 -40
  431. data/test/unit/transition/transition_with_multiple_failure_callbacks_test.rb +0 -40
  432. data/test/unit/transition/transition_with_namespace_test.rb +0 -47
  433. data/test/unit/transition/transition_with_perform_arguments_test.rb +0 -35
  434. data/test/unit/transition/transition_with_transactions_test.rb +0 -42
  435. data/test/unit/transition/transition_without_callbacks_test.rb +0 -33
  436. data/test/unit/transition/transition_without_reading_state_test.rb +0 -22
  437. data/test/unit/transition/transition_without_running_action_test.rb +0 -47
  438. data/test/unit/transition_collection/attribute_transition_collection_by_default_test.rb +0 -23
  439. data/test/unit/transition_collection/attribute_transition_collection_marshalling_test.rb +0 -64
  440. data/test/unit/transition_collection/attribute_transition_collection_with_action_error_test.rb +0 -44
  441. data/test/unit/transition_collection/attribute_transition_collection_with_action_failed_test.rb +0 -44
  442. data/test/unit/transition_collection/attribute_transition_collection_with_after_callback_error_test.rb +0 -32
  443. data/test/unit/transition_collection/attribute_transition_collection_with_after_callback_halt_test.rb +0 -33
  444. data/test/unit/transition_collection/attribute_transition_collection_with_around_after_yield_callback_error_test.rb +0 -32
  445. data/test/unit/transition_collection/attribute_transition_collection_with_around_callback_after_yield_error_test.rb +0 -32
  446. data/test/unit/transition_collection/attribute_transition_collection_with_around_callback_after_yield_halt_test.rb +0 -33
  447. data/test/unit/transition_collection/attribute_transition_collection_with_around_callback_before_yield_halt_test.rb +0 -33
  448. data/test/unit/transition_collection/attribute_transition_collection_with_before_callback_error_test.rb +0 -32
  449. data/test/unit/transition_collection/attribute_transition_collection_with_before_callback_halt_test.rb +0 -33
  450. data/test/unit/transition_collection/attribute_transition_collection_with_callbacks_test.rb +0 -68
  451. data/test/unit/transition_collection/attribute_transition_collection_with_event_transitions_test.rb +0 -41
  452. data/test/unit/transition_collection/attribute_transition_collection_with_events_test.rb +0 -44
  453. data/test/unit/transition_collection/attribute_transition_collection_with_skipped_after_callbacks_test.rb +0 -42
  454. data/test/unit/transition_collection/transition_collection_by_default_test.rb +0 -23
  455. data/test/unit/transition_collection/transition_collection_empty_with_block_test.rb +0 -23
  456. data/test/unit/transition_collection/transition_collection_empty_without_block_test.rb +0 -12
  457. data/test/unit/transition_collection/transition_collection_invalid_test.rb +0 -21
  458. data/test/unit/transition_collection/transition_collection_partial_invalid_test.rb +0 -69
  459. data/test/unit/transition_collection/transition_collection_test.rb +0 -26
  460. data/test/unit/transition_collection/transition_collection_valid_test.rb +0 -57
  461. data/test/unit/transition_collection/transition_collection_with_action_error_test.rb +0 -66
  462. data/test/unit/transition_collection/transition_collection_with_action_failed_test.rb +0 -60
  463. data/test/unit/transition_collection/transition_collection_with_action_hook_and_block_test.rb +0 -17
  464. data/test/unit/transition_collection/transition_collection_with_action_hook_and_skipped_action_test.rb +0 -17
  465. data/test/unit/transition_collection/transition_collection_with_action_hook_and_skipped_after_callbacks_test.rb +0 -37
  466. data/test/unit/transition_collection/transition_collection_with_action_hook_base_test.rb +0 -34
  467. data/test/unit/transition_collection/transition_collection_with_action_hook_error_test.rb +0 -29
  468. data/test/unit/transition_collection/transition_collection_with_action_hook_invalid_test.rb +0 -17
  469. data/test/unit/transition_collection/transition_collection_with_action_hook_multiple_test.rb +0 -79
  470. data/test/unit/transition_collection/transition_collection_with_action_hook_test.rb +0 -45
  471. data/test/unit/transition_collection/transition_collection_with_action_hook_with_different_actions_test.rb +0 -48
  472. data/test/unit/transition_collection/transition_collection_with_action_hook_with_nil_action_test.rb +0 -42
  473. data/test/unit/transition_collection/transition_collection_with_after_callback_halt_test.rb +0 -47
  474. data/test/unit/transition_collection/transition_collection_with_before_callback_halt_test.rb +0 -51
  475. data/test/unit/transition_collection/transition_collection_with_block_test.rb +0 -46
  476. data/test/unit/transition_collection/transition_collection_with_callbacks_test.rb +0 -135
  477. data/test/unit/transition_collection/transition_collection_with_different_actions_test.rb +0 -189
  478. data/test/unit/transition_collection/transition_collection_with_duplicate_actions_test.rb +0 -48
  479. data/test/unit/transition_collection/transition_collection_with_empty_actions_test.rb +0 -41
  480. data/test/unit/transition_collection/transition_collection_with_mixed_actions_test.rb +0 -41
  481. data/test/unit/transition_collection/transition_collection_with_skipped_actions_and_block_test.rb +0 -34
  482. data/test/unit/transition_collection/transition_collection_with_skipped_actions_test.rb +0 -69
  483. data/test/unit/transition_collection/transition_collection_with_skipped_after_callbacks_and_around_callbacks_test.rb +0 -53
  484. data/test/unit/transition_collection/transition_collection_with_skipped_after_callbacks_test.rb +0 -34
  485. data/test/unit/transition_collection/transition_collection_with_transactions_test.rb +0 -65
  486. data/test/unit/transition_collection/transition_collection_without_transactions_test.rb +0 -29
@@ -0,0 +1,896 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StateMachines
4
+ # Test helper module providing assertion methods for state machine testing
5
+ # Designed to work with Minitest, RSpec, and future testing frameworks
6
+ #
7
+ # @example Basic usage with Minitest
8
+ # class MyModelTest < Minitest::Test
9
+ # include StateMachines::TestHelper
10
+ #
11
+ # def test_initial_state
12
+ # model = MyModel.new
13
+ # assert_state(model, :state_machine_name, :initial_state)
14
+ # end
15
+ # end
16
+ #
17
+ # @example Usage with RSpec
18
+ # RSpec.describe MyModel do
19
+ # include StateMachines::TestHelper
20
+ #
21
+ # it "starts in initial state" do
22
+ # model = MyModel.new
23
+ # assert_state(model, :state_machine_name, :initial_state)
24
+ # end
25
+ # end
26
+ #
27
+ # @since 0.10.0
28
+ module TestHelper
29
+ # Assert that an object is in a specific state for a given state machine
30
+ #
31
+ # @param object [Object] The object with state machines
32
+ # @param expected_state [Symbol] The expected state
33
+ # @param machine_name [Symbol] The name of the state machine (defaults to :state)
34
+ # @param message [String, nil] Custom failure message
35
+ # @return [void]
36
+ # @raise [AssertionError] If the state doesn't match
37
+ #
38
+ # @example
39
+ # user = User.new
40
+ # assert_sm_state(user, :active) # Uses default :state machine
41
+ # assert_sm_state(user, :active, machine_name: :status) # Uses :status machine
42
+ def assert_sm_state(object, expected_state, machine_name: :state, message: nil)
43
+ name_method = "#{machine_name}_name"
44
+
45
+ # Handle the case where machine_name doesn't have a corresponding _name method
46
+ unless object.respond_to?(name_method)
47
+ available_machines = begin
48
+ object.class.state_machines.keys
49
+ rescue StandardError
50
+ []
51
+ end
52
+ raise ArgumentError, "No state machine '#{machine_name}' found. Available machines: #{available_machines.inspect}"
53
+ end
54
+
55
+ actual = object.send(name_method)
56
+ default_message = "Expected #{object.class}##{machine_name} to be #{expected_state}, but was #{actual}"
57
+
58
+ if defined?(::Minitest)
59
+ assert_equal expected_state.to_s, actual.to_s, message || default_message
60
+ elsif defined?(::RSpec)
61
+ expect(actual.to_s).to eq(expected_state.to_s), message || default_message
62
+ else
63
+ raise "Expected #{expected_state}, but got #{actual}" unless expected_state.to_s == actual.to_s
64
+ end
65
+ end
66
+
67
+ # Assert that an object can transition via a specific event
68
+ #
69
+ # @param object [Object] The object with state machines
70
+ # @param event [Symbol] The event name
71
+ # @param machine_name [Symbol] The name of the state machine (defaults to :state)
72
+ # @param message [String, nil] Custom failure message
73
+ # @return [void]
74
+ # @raise [AssertionError] If the transition is not available
75
+ #
76
+ # @example
77
+ # user = User.new
78
+ # assert_sm_can_transition(user, :activate) # Uses default :state machine
79
+ # assert_sm_can_transition(user, :activate, machine_name: :status) # Uses :status machine
80
+ def assert_sm_can_transition(object, event, machine_name: :state, message: nil)
81
+ # Try different method naming patterns
82
+ possible_methods = [
83
+ "can_#{event}?", # Default state machine or non-namespaced
84
+ "can_#{event}_#{machine_name}?" # Namespaced events
85
+ ]
86
+
87
+ can_method = possible_methods.find { |method| object.respond_to?(method) }
88
+
89
+ unless can_method
90
+ available_methods = object.methods.grep(/^can_.*\?$/).sort
91
+ raise ArgumentError, "No transition method found for event :#{event} on machine :#{machine_name}. Available methods: #{available_methods.first(10).inspect}"
92
+ end
93
+
94
+ default_message = "Expected to be able to trigger event :#{event} on #{machine_name}, but #{can_method} returned false"
95
+
96
+ if defined?(::Minitest)
97
+ assert object.send(can_method), message || default_message
98
+ elsif defined?(::RSpec)
99
+ expect(object.send(can_method)).to be_truthy, message || default_message
100
+ else
101
+ raise default_message unless object.send(can_method)
102
+ end
103
+ end
104
+
105
+ # Assert that an object cannot transition via a specific event
106
+ #
107
+ # @param object [Object] The object with state machines
108
+ # @param event [Symbol] The event name
109
+ # @param machine_name [Symbol] The name of the state machine (defaults to :state)
110
+ # @param message [String, nil] Custom failure message
111
+ # @return [void]
112
+ # @raise [AssertionError] If the transition is available
113
+ #
114
+ # @example
115
+ # user = User.new
116
+ # assert_sm_cannot_transition(user, :delete) # Uses default :state machine
117
+ # assert_sm_cannot_transition(user, :delete, machine_name: :status) # Uses :status machine
118
+ def assert_sm_cannot_transition(object, event, machine_name: :state, message: nil)
119
+ # Try different method naming patterns
120
+ possible_methods = [
121
+ "can_#{event}?", # Default state machine or non-namespaced
122
+ "can_#{event}_#{machine_name}?" # Namespaced events
123
+ ]
124
+
125
+ can_method = possible_methods.find { |method| object.respond_to?(method) }
126
+
127
+ unless can_method
128
+ available_methods = object.methods.grep(/^can_.*\?$/).sort
129
+ raise ArgumentError, "No transition method found for event :#{event} on machine :#{machine_name}. Available methods: #{available_methods.first(10).inspect}"
130
+ end
131
+
132
+ default_message = "Expected not to be able to trigger event :#{event} on #{machine_name}, but #{can_method} returned true"
133
+
134
+ if defined?(::Minitest)
135
+ refute object.send(can_method), message || default_message
136
+ elsif defined?(::RSpec)
137
+ expect(object.send(can_method)).to be_falsy, message || default_message
138
+ elsif object.send(can_method)
139
+ raise default_message
140
+ end
141
+ end
142
+
143
+ # Assert that triggering an event changes the object to the expected state
144
+ #
145
+ # @param object [Object] The object with state machines
146
+ # @param event [Symbol] The event to trigger
147
+ # @param expected_state [Symbol] The expected state after transition
148
+ # @param machine_name [Symbol] The name of the state machine (defaults to :state)
149
+ # @param message [String, nil] Custom failure message
150
+ # @return [void]
151
+ # @raise [AssertionError] If the transition fails or results in wrong state
152
+ #
153
+ # @example
154
+ # user = User.new
155
+ # assert_sm_transition(user, :activate, :active) # Uses default :state machine
156
+ # assert_sm_transition(user, :activate, :active, machine_name: :status) # Uses :status machine
157
+ def assert_sm_transition(object, event, expected_state, machine_name: :state, message: nil)
158
+ object.send("#{event}!")
159
+ assert_sm_state(object, expected_state, machine_name: machine_name, message: message)
160
+ end
161
+
162
+ # === Extended State Machine Assertions ===
163
+
164
+ def assert_sm_states_list(machine, expected_states, message = nil)
165
+ actual_states = machine.states.map(&:name).compact
166
+ default_message = "Expected states #{expected_states} but got #{actual_states}"
167
+
168
+ if defined?(::Minitest)
169
+ assert_equal expected_states.sort, actual_states.sort, message || default_message
170
+ elsif defined?(::RSpec)
171
+ expect(actual_states.sort).to eq(expected_states.sort), message || default_message
172
+ else
173
+ raise default_message unless expected_states.sort == actual_states.sort
174
+ end
175
+ end
176
+
177
+ def refute_sm_state_defined(machine, state, message = nil)
178
+ state_exists = machine.states.any? { |s| s.name == state }
179
+ default_message = "Expected state #{state} to not be defined in machine"
180
+
181
+ if defined?(::Minitest)
182
+ refute state_exists, message || default_message
183
+ elsif defined?(::RSpec)
184
+ expect(state_exists).to be_falsy, message || default_message
185
+ elsif state_exists
186
+ raise default_message
187
+ end
188
+ end
189
+ alias assert_sm_state_not_defined refute_sm_state_defined
190
+
191
+ def assert_sm_initial_state(machine, expected_state, message = nil)
192
+ state_obj = machine.state(expected_state)
193
+ is_initial = state_obj&.initial?
194
+ default_message = "Expected state #{expected_state} to be the initial state"
195
+
196
+ if defined?(::Minitest)
197
+ assert is_initial, message || default_message
198
+ elsif defined?(::RSpec)
199
+ expect(is_initial).to be_truthy, message || default_message
200
+ else
201
+ raise default_message unless is_initial
202
+ end
203
+ end
204
+
205
+ def assert_sm_final_state(machine, state, message = nil)
206
+ state_obj = machine.states[state]
207
+ is_final = state_obj&.final?
208
+ default_message = "Expected state #{state} to be final"
209
+
210
+ if defined?(::Minitest)
211
+ assert is_final, message || default_message
212
+ elsif defined?(::RSpec)
213
+ expect(is_final).to be_truthy, message || default_message
214
+ else
215
+ raise default_message unless is_final
216
+ end
217
+ end
218
+
219
+ def assert_sm_possible_transitions(machine, from:, expected_to_states:, message: nil)
220
+ actual_transitions = machine.events.flat_map do |event|
221
+ event.branches.select { |branch| branch.known_states.include?(from) }
222
+ .map(&:to)
223
+ end.uniq
224
+ default_message = "Expected transitions from #{from} to #{expected_to_states} but got #{actual_transitions}"
225
+
226
+ if defined?(::Minitest)
227
+ assert_equal expected_to_states.sort, actual_transitions.sort, message || default_message
228
+ elsif defined?(::RSpec)
229
+ expect(actual_transitions.sort).to eq(expected_to_states.sort), message || default_message
230
+ else
231
+ raise default_message unless expected_to_states.sort == actual_transitions.sort
232
+ end
233
+ end
234
+
235
+ def refute_sm_transition_allowed(machine, from:, to:, on:, message: nil)
236
+ event = machine.events[on]
237
+ is_allowed = event&.branches&.any? { |branch| branch.known_states.include?(from) && branch.to == to }
238
+ default_message = "Expected transition from #{from} to #{to} on #{on} to not be allowed"
239
+
240
+ if defined?(::Minitest)
241
+ refute is_allowed, message || default_message
242
+ elsif defined?(::RSpec)
243
+ expect(is_allowed).to be_falsy, message || default_message
244
+ elsif is_allowed
245
+ raise default_message
246
+ end
247
+ end
248
+ alias assert_sm_transition_not_allowed refute_sm_transition_allowed
249
+
250
+ def assert_sm_event_triggers(object, event, machine_name = :state, message = nil)
251
+ initial_state = object.send(machine_name)
252
+ object.send("#{event}!")
253
+ state_changed = initial_state != object.send(machine_name)
254
+ default_message = "Expected event #{event} to trigger state change on #{machine_name}"
255
+
256
+ if defined?(::Minitest)
257
+ assert state_changed, message || default_message
258
+ elsif defined?(::RSpec)
259
+ expect(state_changed).to be_truthy, message || default_message
260
+ else
261
+ raise default_message unless state_changed
262
+ end
263
+ end
264
+
265
+ def refute_sm_event_triggers(object, event, machine_name = :state, message = nil)
266
+ initial_state = object.send(machine_name)
267
+ begin
268
+ object.send("#{event}!")
269
+ state_unchanged = initial_state == object.send(machine_name)
270
+ default_message = "Expected event #{event} to not trigger state change on #{machine_name}"
271
+
272
+ if defined?(::Minitest)
273
+ assert state_unchanged, message || default_message
274
+ elsif defined?(::RSpec)
275
+ expect(state_unchanged).to be_truthy, message || default_message
276
+ else
277
+ raise default_message unless state_unchanged
278
+ end
279
+ rescue StateMachines::InvalidTransition
280
+ # Expected behavior - transition was blocked
281
+ end
282
+ end
283
+ alias assert_sm_event_not_triggers refute_sm_event_triggers
284
+
285
+ def assert_sm_event_raises_error(object, event, error_class, message = nil)
286
+ default_message = "Expected event #{event} to raise #{error_class}"
287
+
288
+ if defined?(::Minitest)
289
+ assert_raises(error_class, message || default_message) do
290
+ object.send("#{event}!")
291
+ end
292
+ elsif defined?(::RSpec)
293
+ expect { object.send("#{event}!") }.to raise_error(error_class), message || default_message
294
+ else
295
+ begin
296
+ object.send("#{event}!")
297
+ raise default_message
298
+ rescue error_class
299
+ # Expected behavior
300
+ end
301
+ end
302
+ end
303
+
304
+ def assert_sm_callback_executed(object, callback_name, message = nil)
305
+ callbacks_executed = object.instance_variable_get(:@_sm_callbacks_executed) || []
306
+ callback_was_executed = callbacks_executed.include?(callback_name)
307
+ default_message = "Expected callback #{callback_name} to be executed"
308
+
309
+ if defined?(::Minitest)
310
+ assert callback_was_executed, message || default_message
311
+ elsif defined?(::RSpec)
312
+ expect(callback_was_executed).to be_truthy, message || default_message
313
+ else
314
+ raise default_message unless callback_was_executed
315
+ end
316
+ end
317
+
318
+ def refute_sm_callback_executed(object, callback_name, message = nil)
319
+ callbacks_executed = object.instance_variable_get(:@_sm_callbacks_executed) || []
320
+ callback_was_executed = callbacks_executed.include?(callback_name)
321
+ default_message = "Expected callback #{callback_name} to not be executed"
322
+
323
+ if defined?(::Minitest)
324
+ refute callback_was_executed, message || default_message
325
+ elsif defined?(::RSpec)
326
+ expect(callback_was_executed).to be_falsy, message || default_message
327
+ elsif callback_was_executed
328
+ raise default_message
329
+ end
330
+ end
331
+ alias assert_sm_callback_not_executed refute_sm_callback_executed
332
+
333
+ # Assert that a record's state is persisted correctly for a specific state machine
334
+ #
335
+ # @param record [Object] The record to check (should respond to reload)
336
+ # @param expected [String, Symbol] The expected persisted state
337
+ # @param machine_name [Symbol] The name of the state machine (defaults to :state)
338
+ # @param message [String, nil] Custom failure message
339
+ # @return [void]
340
+ # @raise [AssertionError] If the persisted state doesn't match
341
+ #
342
+ # @example
343
+ # # Default state machine
344
+ # assert_sm_state_persisted(user, "active")
345
+ #
346
+ # # Specific state machine
347
+ # assert_sm_state_persisted(ship, "up", :shields)
348
+ # assert_sm_state_persisted(ship, "armed", :weapons)
349
+ def assert_sm_state_persisted(record, expected, machine_name = :state, message = nil)
350
+ record.reload if record.respond_to?(:reload)
351
+ actual_state = record.send(machine_name)
352
+ default_message = "Expected persisted state #{expected} for #{machine_name} but got #{actual_state}"
353
+
354
+ if defined?(::Minitest)
355
+ assert_equal expected, actual_state, message || default_message
356
+ elsif defined?(::RSpec)
357
+ expect(actual_state).to eq(expected), message || default_message
358
+ else
359
+ raise default_message unless expected == actual_state
360
+ end
361
+ end
362
+
363
+ # Assert that executing a block triggers one or more expected events
364
+ #
365
+ # @param object [Object] The object with state machines
366
+ # @param expected_events [Symbol, Array<Symbol>] The event(s) expected to be triggered
367
+ # @param machine_name [Symbol] The name of the state machine (defaults to :state)
368
+ # @param message [String, nil] Custom failure message
369
+ # @return [void]
370
+ # @raise [AssertionError] If the expected events were not triggered
371
+ #
372
+ # @example
373
+ # # Single event
374
+ # assert_sm_triggers_event(vehicle, :crash) { vehicle.redline }
375
+ #
376
+ # # Multiple events
377
+ # assert_sm_triggers_event(vehicle, [:crash, :emergency]) { vehicle.emergency_stop }
378
+ #
379
+ # # Specific machine
380
+ # assert_sm_triggers_event(vehicle, :disable, machine_name: :alarm) { vehicle.turn_off_alarm }
381
+ def assert_sm_triggers_event(object, expected_events, machine_name: :state, message: nil)
382
+ expected_events = Array(expected_events)
383
+ triggered_events = []
384
+
385
+ # Get the state machine
386
+ machine = object.class.state_machines[machine_name]
387
+ raise ArgumentError, "No state machine found for #{machine_name}" unless machine
388
+
389
+ # Save original callbacks to restore later
390
+ machine.callbacks[:before].dup
391
+
392
+ # Add a temporary callback to track triggered events
393
+ temp_callback = machine.before_transition do |_obj, transition|
394
+ triggered_events << transition.event if transition.event
395
+ end
396
+
397
+ begin
398
+ # Execute the block
399
+ yield
400
+
401
+ # Check if expected events were triggered
402
+ missing_events = expected_events - triggered_events
403
+ extra_events = triggered_events - expected_events
404
+
405
+ unless missing_events.empty? && extra_events.empty?
406
+ default_message = "Expected events #{expected_events.inspect} to be triggered, but got #{triggered_events.inspect}"
407
+ default_message += ". Missing: #{missing_events.inspect}" if missing_events.any?
408
+ default_message += ". Extra: #{extra_events.inspect}" if extra_events.any?
409
+
410
+ if defined?(::Minitest)
411
+ assert false, message || default_message
412
+ elsif defined?(::RSpec)
413
+ raise message || default_message
414
+ else
415
+ raise default_message
416
+ end
417
+ end
418
+ ensure
419
+ # Restore original callbacks by removing the temporary one
420
+ machine.callbacks[:before].delete(temp_callback)
421
+ end
422
+ end
423
+
424
+ # Assert that a before_transition callback is defined with expected arguments
425
+ #
426
+ # @param machine_or_class [StateMachines::Machine, Class] The machine or class to check
427
+ # @param options [Hash] Expected callback options (on:, from:, to:, do:, if:, unless:)
428
+ # @param message [String, nil] Custom failure message
429
+ # @return [void]
430
+ # @raise [AssertionError] If the callback is not defined
431
+ #
432
+ # @example
433
+ # # Check for specific transition callback
434
+ # assert_before_transition(Vehicle, on: :crash, do: :emergency_stop)
435
+ #
436
+ # # Check with from/to states
437
+ # assert_before_transition(Vehicle.state_machine, from: :parked, to: :idling, do: :start_engine)
438
+ #
439
+ # # Check with conditions
440
+ # assert_before_transition(Vehicle, on: :ignite, if: :seatbelt_on?)
441
+ def assert_before_transition(machine_or_class, options = {}, message = nil)
442
+ _assert_transition_callback(:before, machine_or_class, options, message)
443
+ end
444
+
445
+ # Assert that an after_transition callback is defined with expected arguments
446
+ #
447
+ # @param machine_or_class [StateMachines::Machine, Class] The machine or class to check
448
+ # @param options [Hash] Expected callback options (on:, from:, to:, do:, if:, unless:)
449
+ # @param message [String, nil] Custom failure message
450
+ # @return [void]
451
+ # @raise [AssertionError] If the callback is not defined
452
+ #
453
+ # @example
454
+ # # Check for specific transition callback
455
+ # assert_after_transition(Vehicle, on: :crash, do: :tow)
456
+ #
457
+ # # Check with from/to states
458
+ # assert_after_transition(Vehicle.state_machine, from: :stalled, to: :parked, do: :log_repair)
459
+ def assert_after_transition(machine_or_class, options = {}, message = nil)
460
+ _assert_transition_callback(:after, machine_or_class, options, message)
461
+ end
462
+
463
+ # === Sync Mode Assertions ===
464
+
465
+ # Assert that a state machine is operating in synchronous mode
466
+ #
467
+ # @param object [Object] The object with state machines
468
+ # @param machine_name [Symbol] The name of the state machine (defaults to :state)
469
+ # @param message [String, nil] Custom failure message
470
+ # @return [void]
471
+ # @raise [AssertionError] If the machine has async mode enabled
472
+ #
473
+ # @example
474
+ # user = User.new
475
+ # assert_sm_sync_mode(user) # Uses default :state machine
476
+ # assert_sm_sync_mode(user, :status) # Uses :status machine
477
+ def assert_sm_sync_mode(object, machine_name = :state, message = nil)
478
+ machine = object.class.state_machines[machine_name]
479
+ raise ArgumentError, "No state machine '#{machine_name}' found" unless machine
480
+
481
+ async_enabled = machine.respond_to?(:async_mode_enabled?) && machine.async_mode_enabled?
482
+ default_message = "Expected state machine '#{machine_name}' to be in sync mode, but async mode is enabled"
483
+
484
+ if defined?(::Minitest)
485
+ refute async_enabled, message || default_message
486
+ elsif defined?(::RSpec)
487
+ expect(async_enabled).to be_falsy, message || default_message
488
+ elsif async_enabled
489
+ raise default_message
490
+ end
491
+ end
492
+
493
+ # Assert that async methods are not available on a sync-only object
494
+ #
495
+ # @param object [Object] The object with state machines
496
+ # @param message [String, nil] Custom failure message
497
+ # @return [void]
498
+ # @raise [AssertionError] If async methods are available
499
+ #
500
+ # @example
501
+ # sync_only_car = Car.new # Car has no async: true machines
502
+ # assert_sm_no_async_methods(sync_only_car)
503
+ def assert_sm_no_async_methods(object, message = nil)
504
+ async_methods = %i[fire_event_async fire_events_async fire_event_async! async_fire_event]
505
+ available_async_methods = async_methods.select { |method| object.respond_to?(method) }
506
+
507
+ default_message = "Expected no async methods to be available, but found: #{available_async_methods.inspect}"
508
+
509
+ if defined?(::Minitest)
510
+ assert_empty available_async_methods, message || default_message
511
+ elsif defined?(::RSpec)
512
+ expect(available_async_methods).to be_empty, message || default_message
513
+ elsif available_async_methods.any?
514
+ raise default_message
515
+ end
516
+ end
517
+
518
+ # Assert that an object has no async-enabled state machines
519
+ #
520
+ # @param object [Object] The object with state machines
521
+ # @param message [String, nil] Custom failure message
522
+ # @return [void]
523
+ # @raise [AssertionError] If any machine has async mode enabled
524
+ #
525
+ # @example
526
+ # sync_only_vehicle = Vehicle.new # All machines are sync-only
527
+ # assert_sm_all_sync(sync_only_vehicle)
528
+ def assert_sm_all_sync(object, message = nil)
529
+ async_machines = []
530
+
531
+ object.class.state_machines.each do |name, machine|
532
+ if machine.respond_to?(:async_mode_enabled?) && machine.async_mode_enabled?
533
+ async_machines << name
534
+ end
535
+ end
536
+
537
+ default_message = "Expected all state machines to be sync-only, but these have async enabled: #{async_machines.inspect}"
538
+
539
+ if defined?(::Minitest)
540
+ assert_empty async_machines, message || default_message
541
+ elsif defined?(::RSpec)
542
+ expect(async_machines).to be_empty, message || default_message
543
+ elsif async_machines.any?
544
+ raise default_message
545
+ end
546
+ end
547
+
548
+ # Assert that synchronous event execution works correctly
549
+ #
550
+ # @param object [Object] The object with state machines
551
+ # @param event [Symbol] The event to trigger
552
+ # @param expected_state [Symbol] The expected state after transition
553
+ # @param machine_name [Symbol] The name of the state machine (defaults to :state)
554
+ # @param message [String, nil] Custom failure message
555
+ # @return [void]
556
+ # @raise [AssertionError] If sync execution fails
557
+ #
558
+ # @example
559
+ # car = Car.new
560
+ # assert_sm_sync_execution(car, :start, :running)
561
+ # assert_sm_sync_execution(car, :turn_on, :active, :alarm)
562
+ def assert_sm_sync_execution(object, event, expected_state, machine_name = :state, message = nil)
563
+ # Store initial state
564
+ initial_state = object.send(machine_name)
565
+
566
+ # Execute event synchronously
567
+ result = object.send("#{event}!")
568
+
569
+ # Verify immediate state change (no async delay)
570
+ final_state = object.send(machine_name)
571
+
572
+ # Check that transition succeeded
573
+ state_changed = initial_state != final_state
574
+ correct_final_state = final_state.to_s == expected_state.to_s
575
+
576
+ default_message = "Expected sync execution of '#{event}' to change #{machine_name} from '#{initial_state}' to '#{expected_state}', but got '#{final_state}'"
577
+
578
+ if defined?(::Minitest)
579
+ assert result, "Event #{event} should return true on success"
580
+ assert state_changed, "State should change from #{initial_state}"
581
+ assert correct_final_state, message || default_message
582
+ elsif defined?(::RSpec)
583
+ expect(result).to be_truthy, "Event #{event} should return true on success"
584
+ expect(state_changed).to be_truthy, "State should change from #{initial_state}"
585
+ expect(correct_final_state).to be_truthy, message || default_message
586
+ else
587
+ raise "Event #{event} should return true on success" unless result
588
+ raise "State should change from #{initial_state}" unless state_changed
589
+ raise default_message unless correct_final_state
590
+ end
591
+ end
592
+
593
+ # Assert that event execution is immediate (no async delay)
594
+ #
595
+ # @param object [Object] The object with state machines
596
+ # @param event [Symbol] The event to trigger
597
+ # @param machine_name [Symbol] The name of the state machine (defaults to :state)
598
+ # @param message [String, nil] Custom failure message
599
+ # @return [void]
600
+ # @raise [AssertionError] If execution appears to be async
601
+ #
602
+ # @example
603
+ # car = Car.new
604
+ # assert_sm_immediate_execution(car, :start)
605
+ def assert_sm_immediate_execution(object, event, machine_name = :state, message = nil)
606
+ initial_state = object.send(machine_name)
607
+
608
+ # Record start time and execute
609
+ start_time = Time.now
610
+ object.send("#{event}!")
611
+ execution_time = Time.now - start_time
612
+
613
+ final_state = object.send(machine_name)
614
+ state_changed = initial_state != final_state
615
+
616
+ # Should complete very quickly (under 10ms for sync operations)
617
+ is_immediate = execution_time < 0.01
618
+
619
+ default_message = "Expected immediate sync execution of '#{event}', but took #{execution_time}s (likely async)"
620
+
621
+ if defined?(::Minitest)
622
+ assert state_changed, "Event should trigger state change"
623
+ assert is_immediate, message || default_message
624
+ elsif defined?(::RSpec)
625
+ expect(state_changed).to be_truthy, "Event should trigger state change"
626
+ expect(is_immediate).to be_truthy, message || default_message
627
+ else
628
+ raise "Event should trigger state change" unless state_changed
629
+ raise default_message unless is_immediate
630
+ end
631
+ end
632
+
633
+ # === Async Mode Assertions ===
634
+
635
+ # Assert that a state machine is operating in asynchronous mode
636
+ #
637
+ # @param object [Object] The object with state machines
638
+ # @param machine_name [Symbol] The name of the state machine (defaults to :state)
639
+ # @param message [String, nil] Custom failure message
640
+ # @return [void]
641
+ # @raise [AssertionError] If the machine doesn't have async mode enabled
642
+ #
643
+ # @example
644
+ # drone = AutonomousDrone.new
645
+ # assert_sm_async_mode(drone) # Uses default :state machine
646
+ # assert_sm_async_mode(drone, :teleporter_status) # Uses :teleporter_status machine
647
+ def assert_sm_async_mode(object, machine_name = :state, message = nil)
648
+ machine = object.class.state_machines[machine_name]
649
+ raise ArgumentError, "No state machine '#{machine_name}' found" unless machine
650
+
651
+ async_enabled = machine.respond_to?(:async_mode_enabled?) && machine.async_mode_enabled?
652
+ default_message = "Expected state machine '#{machine_name}' to have async mode enabled, but it's in sync mode"
653
+
654
+ if defined?(::Minitest)
655
+ assert async_enabled, message || default_message
656
+ elsif defined?(::RSpec)
657
+ expect(async_enabled).to be_truthy, message || default_message
658
+ else
659
+ raise default_message unless async_enabled
660
+ end
661
+ end
662
+
663
+ # Assert that async methods are available on an async-enabled object
664
+ #
665
+ # @param object [Object] The object with state machines
666
+ # @param message [String, nil] Custom failure message
667
+ # @return [void]
668
+ # @raise [AssertionError] If async methods are not available
669
+ #
670
+ # @example
671
+ # drone = AutonomousDrone.new # Has async: true machines
672
+ # assert_sm_async_methods(drone)
673
+ def assert_sm_async_methods(object, message = nil)
674
+ async_methods = %i[fire_event_async fire_events_async fire_event_async! async_fire_event]
675
+ available_async_methods = async_methods.select { |method| object.respond_to?(method) }
676
+
677
+ default_message = "Expected async methods to be available, but found none"
678
+
679
+ if defined?(::Minitest)
680
+ refute_empty available_async_methods, message || default_message
681
+ elsif defined?(::RSpec)
682
+ expect(available_async_methods).not_to be_empty, message || default_message
683
+ elsif available_async_methods.empty?
684
+ raise default_message
685
+ end
686
+ end
687
+
688
+ # Assert that an object has async-enabled state machines
689
+ #
690
+ # @param object [Object] The object with state machines
691
+ # @param machine_names [Array<Symbol>] Expected async machine names
692
+ # @param message [String, nil] Custom failure message
693
+ # @return [void]
694
+ # @raise [AssertionError] If expected machines don't have async mode
695
+ #
696
+ # @example
697
+ # drone = AutonomousDrone.new
698
+ # assert_sm_has_async(drone, [:status, :teleporter_status, :shields])
699
+ def assert_sm_has_async(object, machine_names = nil, message = nil)
700
+ if machine_names
701
+ # Check specific machines
702
+ non_async_machines = machine_names.reject do |name|
703
+ machine = object.class.state_machines[name]
704
+ machine&.respond_to?(:async_mode_enabled?) && machine.async_mode_enabled?
705
+ end
706
+
707
+ default_message = "Expected machines #{machine_names.inspect} to have async enabled, but these don't: #{non_async_machines.inspect}"
708
+
709
+ if defined?(::Minitest)
710
+ assert_empty non_async_machines, message || default_message
711
+ elsif defined?(::RSpec)
712
+ expect(non_async_machines).to be_empty, message || default_message
713
+ elsif non_async_machines.any?
714
+ raise default_message
715
+ end
716
+ else
717
+ # Check that at least one machine has async
718
+ async_machines = object.class.state_machines.select do |name, machine|
719
+ machine.respond_to?(:async_mode_enabled?) && machine.async_mode_enabled?
720
+ end
721
+
722
+ default_message = "Expected at least one state machine to have async enabled, but none found"
723
+
724
+ if defined?(::Minitest)
725
+ refute_empty async_machines, message || default_message
726
+ elsif defined?(::RSpec)
727
+ expect(async_machines).not_to be_empty, message || default_message
728
+ elsif async_machines.empty?
729
+ raise default_message
730
+ end
731
+ end
732
+ end
733
+
734
+ # Assert that individual async event methods are available
735
+ #
736
+ # @param object [Object] The object with state machines
737
+ # @param event [Symbol] The event name
738
+ # @param message [String, nil] Custom failure message
739
+ # @return [void]
740
+ # @raise [AssertionError] If async event methods are not available
741
+ #
742
+ # @example
743
+ # drone = AutonomousDrone.new
744
+ # assert_sm_async_event_methods(drone, :launch) # Checks launch_async and launch_async!
745
+ def assert_sm_async_event_methods(object, event, message = nil)
746
+ async_method = "#{event}_async".to_sym
747
+ async_bang_method = "#{event}_async!".to_sym
748
+
749
+ has_async = object.respond_to?(async_method)
750
+ has_async_bang = object.respond_to?(async_bang_method)
751
+
752
+ default_message = "Expected async event methods #{async_method} and #{async_bang_method} to be available for event :#{event}"
753
+
754
+ if defined?(::Minitest)
755
+ assert has_async, "Missing #{async_method} method"
756
+ assert has_async_bang, "Missing #{async_bang_method} method"
757
+ elsif defined?(::RSpec)
758
+ expect(has_async).to be_truthy, "Missing #{async_method} method"
759
+ expect(has_async_bang).to be_truthy, "Missing #{async_bang_method} method"
760
+ else
761
+ raise "Missing #{async_method} method" unless has_async
762
+ raise "Missing #{async_bang_method} method" unless has_async_bang
763
+ end
764
+ end
765
+
766
+ # Assert that an object has thread-safe state methods when async is enabled
767
+ #
768
+ # @param object [Object] The object with state machines
769
+ # @param message [String, nil] Custom failure message
770
+ # @return [void]
771
+ # @raise [AssertionError] If thread-safe methods are not available
772
+ #
773
+ # @example
774
+ # drone = AutonomousDrone.new
775
+ # assert_sm_thread_safe_methods(drone)
776
+ def assert_sm_thread_safe_methods(object, message = nil)
777
+ thread_safe_methods = %i[state_machine_mutex read_state_safely write_state_safely]
778
+ missing_methods = thread_safe_methods.reject { |method| object.respond_to?(method) }
779
+
780
+ default_message = "Expected thread-safe methods to be available, but missing: #{missing_methods.inspect}"
781
+
782
+ if defined?(::Minitest)
783
+ assert_empty missing_methods, message || default_message
784
+ elsif defined?(::RSpec)
785
+ expect(missing_methods).to be_empty, message || default_message
786
+ elsif missing_methods.any?
787
+ raise default_message
788
+ end
789
+ end
790
+
791
+ # RSpec-style aliases for event triggering (for consistency with RSpec expectations)
792
+ alias expect_to_trigger_event assert_sm_triggers_event
793
+ alias have_triggered_event assert_sm_triggers_event
794
+
795
+ private
796
+
797
+ # Internal helper for checking transition callbacks
798
+ def _assert_transition_callback(callback_type, machine_or_class, options, message)
799
+ # Get the machine
800
+ machine = machine_or_class.is_a?(StateMachines::Machine) ? machine_or_class : machine_or_class.state_machine
801
+ raise ArgumentError, 'No state machine found' unless machine
802
+
803
+ callbacks = machine.callbacks[callback_type] || []
804
+
805
+ # Extract expected conditions
806
+ expected_event = options[:on]
807
+ expected_from = options[:from]
808
+ expected_to = options[:to]
809
+ expected_method = options[:do]
810
+ expected_if = options[:if]
811
+ expected_unless = options[:unless]
812
+
813
+ # Find matching callback
814
+ matching_callback = callbacks.find do |callback|
815
+ branch = callback.branch
816
+
817
+ # Check event requirement
818
+ if expected_event
819
+ event_requirement = branch.event_requirement
820
+ event_matches = if event_requirement && event_requirement.respond_to?(:values)
821
+ event_requirement.values.include?(expected_event)
822
+ else
823
+ false
824
+ end
825
+ next false unless event_matches
826
+ end
827
+
828
+ # Check state requirements (from/to)
829
+ if expected_from || expected_to
830
+ state_matches = false
831
+ branch.state_requirements.each do |req|
832
+ from_matches = !expected_from || (req[:from] && req[:from].respond_to?(:values) && req[:from].values.include?(expected_from))
833
+ to_matches = !expected_to || (req[:to] && req[:to].respond_to?(:values) && req[:to].values.include?(expected_to))
834
+
835
+ if from_matches && to_matches
836
+ state_matches = true
837
+ break
838
+ end
839
+ end
840
+ next false unless state_matches
841
+ end
842
+
843
+ # Check method requirement
844
+ if expected_method
845
+ methods = callback.instance_variable_get(:@methods) || []
846
+ method_matches = methods.any? do |method|
847
+ (method.is_a?(Symbol) && method == expected_method) ||
848
+ (method.is_a?(String) && method.to_sym == expected_method) ||
849
+ (method.respond_to?(:call) && method.respond_to?(:source_location))
850
+ end
851
+ next false unless method_matches
852
+ end
853
+
854
+ # Check if condition
855
+ if expected_if
856
+ if_condition = branch.if_condition
857
+ if_matches = (if_condition.is_a?(Symbol) && if_condition == expected_if) ||
858
+ (if_condition.is_a?(String) && if_condition.to_sym == expected_if) ||
859
+ if_condition.respond_to?(:call)
860
+ next false unless if_matches
861
+ end
862
+
863
+ # Check unless condition
864
+ if expected_unless
865
+ unless_condition = branch.unless_condition
866
+ unless_matches = (unless_condition.is_a?(Symbol) && unless_condition == expected_unless) ||
867
+ (unless_condition.is_a?(String) && unless_condition.to_sym == expected_unless) ||
868
+ unless_condition.respond_to?(:call)
869
+ next false unless unless_matches
870
+ end
871
+
872
+ true
873
+ end
874
+
875
+ return if matching_callback
876
+
877
+ expected_parts = []
878
+ expected_parts << "on: #{expected_event.inspect}" if expected_event
879
+ expected_parts << "from: #{expected_from.inspect}" if expected_from
880
+ expected_parts << "to: #{expected_to.inspect}" if expected_to
881
+ expected_parts << "do: #{expected_method.inspect}" if expected_method
882
+ expected_parts << "if: #{expected_if.inspect}" if expected_if
883
+ expected_parts << "unless: #{expected_unless.inspect}" if expected_unless
884
+
885
+ default_message = "Expected #{callback_type}_transition callback with #{expected_parts.join(', ')} to be defined, but it was not found"
886
+
887
+ if defined?(::Minitest)
888
+ assert false, message || default_message
889
+ elsif defined?(::RSpec)
890
+ raise message || default_message
891
+ else
892
+ raise default_message
893
+ end
894
+ end
895
+ end
896
+ end