state_machines-graphviz 0.0.1 → 0.1.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 (41) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE.txt +1 -1
  3. data/README.md +71 -5
  4. data/lib/state_machines/graphviz/graph.rb +5 -5
  5. data/lib/state_machines/graphviz/renderer.rb +155 -0
  6. data/lib/state_machines/graphviz/version.rb +3 -1
  7. data/lib/state_machines/graphviz.rb +35 -1
  8. data/lib/state_machines/tasks/railtie.rb +11 -0
  9. data/lib/state_machines/tasks/state_machines.rake +5 -0
  10. data/lib/state_machines-graphviz.rb +8 -0
  11. data/{spec → test}/files/switch.rb +2 -0
  12. data/test/graph_default_test.rb +24 -0
  13. data/test/graph_edges_test.rb +28 -0
  14. data/test/graph_nodes_test.rb +25 -0
  15. data/test/graph_output_test.rb +21 -0
  16. data/test/machine_class_drawing_test.rb +28 -0
  17. data/test/machine_drawing_test.rb +72 -0
  18. data/test/machine_drawing_with_dynamic_states_test.rb +38 -0
  19. data/test/machine_drawing_with_integer_states_test.rb +38 -0
  20. data/test/machine_drawing_with_nil_states_test.rb +37 -0
  21. data/test/state_drawing_final_test.rb +17 -0
  22. data/test/state_drawing_initial_test.rb +25 -0
  23. data/test/state_drawing_lambda_value_test.rb +21 -0
  24. data/test/state_drawing_nil_name_test.rb +22 -0
  25. data/test/state_drawing_non_final_test.rb +20 -0
  26. data/test/state_drawing_test.rb +33 -0
  27. data/test/state_drawing_with_human_name_test.rb +20 -0
  28. data/test/test_helper.rb +6 -0
  29. metadata +64 -28
  30. data/.gitignore +0 -22
  31. data/.rspec +0 -3
  32. data/.travis.yaml +0 -10
  33. data/Gemfile +0 -4
  34. data/Rakefile +0 -10
  35. data/lib/state_machines/graphviz/monkeypatch.rb +0 -161
  36. data/spec/graph_spec.rb +0 -92
  37. data/spec/machine_drawing_spec.rb +0 -215
  38. data/spec/machine_spec.rb +0 -19
  39. data/spec/spec_helper.rb +0 -2
  40. data/spec/state_spec.rb +0 -142
  41. data/state_machines-graphviz.gemspec +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: b375be8fce2b404843e60ffc947edb7d7b37a5e4
4
- data.tar.gz: e46a7734205fd1ff0946dacb72195e82e1863d5a
2
+ SHA256:
3
+ metadata.gz: 90020c1871d32a31894619ff9883ae27e08210499cd4a1c0361b18ad503c04b1
4
+ data.tar.gz: 9f206b3af3589f1072c8514b182d57c066e1de9179effafb6351a63471f8d61f
5
5
  SHA512:
6
- metadata.gz: db7731a6a0236d51ca0a888870ab4f0bf1670ade1832051b2e84bd7220290b8ac5b86d473c089608aeb667388c70665fb9f2acf10cc89d59ee57d7f76fe59965
7
- data.tar.gz: 854bb07b3eff8a50165b6bbc7012e2304259711d768238d5a0fbf88f02898f6e5a9730f36971a5c5d6f810cc87d30a649e9bfbf33a62230e1ff1ef99c7eda6c8
6
+ metadata.gz: cabca272749f9e4e5928cea279b45503e78268b5060e6acb6b50e61fcd5ef31800537ad5da64e7730afceeffaf3f560f18d8e0e96104ab8e00cab15acc27371f
7
+ data.tar.gz: d410ac462911bd82ae5827c71045cef33b4e1413d0abb079f1e1124c1f58fe337b2c4313d2281aabc39bf9a51e92cb7666c191972c365badd83f4feea9749b90
data/LICENSE.txt CHANGED
@@ -1,5 +1,5 @@
1
1
  Copyright (c) 2006-2012 Aaron Pfeifer
2
- Copyright (c) 2014 Abdelkader Boudih
2
+ Copyright (c) 2014-2023 Abdelkader Boudih
3
3
 
4
4
  MIT License
5
5
 
data/README.md CHANGED
@@ -1,12 +1,13 @@
1
1
  # StateMachines::Graphviz
2
2
 
3
- TODO: Write a gem description
3
+ This adds support for generating di-graphs based on the
4
+ events, states, and transitions defined for a state machine using [GraphViz](http://www.graphviz.org).
4
5
 
5
6
  ## Installation
6
7
 
7
8
  Add this line to your application's Gemfile:
8
9
 
9
- gem 'state_machine2-graphviz' , group: :development
10
+ gem 'state_machines-graphviz' , group: :development
10
11
 
11
12
  And then execute:
12
13
 
@@ -14,15 +15,80 @@ And then execute:
14
15
 
15
16
  Or install it yourself as:
16
17
 
17
- $ gem install state_machine2-graphviz
18
+ $ gem install state_machines-graphviz
18
19
 
19
20
  ## Usage
20
21
 
21
- TODO: Write usage instructions here
22
+ The default output folder for the images is `doc/state_machines`.
23
+
24
+ #### Examples
25
+
26
+ To generate a graph for a specific file / class:
27
+
28
+ ```bash
29
+ rake state_machines:draw FILE=vehicle.rb CLASS=Vehicle
30
+ ```
31
+ From a Rails app directory:
32
+
33
+ ```bash
34
+ rake state_machines:draw CLASS=Vehicle
35
+ ```
36
+
37
+ To save files to a specific path:
38
+
39
+ ```bash
40
+ rake state_machines:draw FILE=vehicle.rb CLASS=Vehicle TARGET=files
41
+ ```
42
+
43
+ To customize the image format / orientation:
44
+
45
+ ```bash
46
+ rake state_machines:draw FILE=vehicle.rb CLASS=Vehicle FORMAT=jpg ORIENTATION=landscape
47
+ ```
48
+
49
+ See http://rdoc.info/github/glejeune/Ruby-Graphviz/GraphViz/Constants for the list of
50
+ supported image formats. If resolution is an issue, the svg format may offer
51
+ better results.
52
+
53
+ To generate multiple state machine graphs:
54
+
55
+ ```bash
56
+ rake state_machines:draw FILE=vehicle.rb,car.rb CLASS=Vehicle,Car
57
+ ```
58
+
59
+ To use human state / event names:
60
+
61
+ ```bash
62
+ rake state_machines:draw FILE=vehicle.rb CLASS=Vehicle HUMAN_NAMES=true
63
+ ```
64
+
65
+ **Note** that this will generate a different file for every state machine defined
66
+ in the class. The generated files will use an output filename of the format
67
+ `#{class_name}_#{machine_name}.#{format}`.
68
+
69
+ For examples of actual images generated using this task, see those under the
70
+ examples folder.
71
+
72
+ #### Example output
73
+
74
+ Generate the sample diagram used in this README:
75
+
76
+ ```bash
77
+ bundle exec ruby script/generate_example.rb
78
+ ```
79
+
80
+ ![Switch state machine](doc/switch.svg)
81
+
82
+ ### Interactive graphs
83
+
84
+ Jean Bovet's [Visual Automata Simulator](https://github.com/NimaGhaedsharafi/VAS)
85
+ is a great tool for "simulating, visualizing and transforming finite state
86
+ automata and Turing Machines". It can help in the creation of states and events
87
+ for your models. It is cross-platform, written in Java.
22
88
 
23
89
  ## Contributing
24
90
 
25
- 1. Fork it ( https://github.com/seuros/state_machine2-graphviz/fork )
91
+ 1. Fork it ( https://github.com/state-machines/state_machines-graphviz/fork )
26
92
  2. Create your feature branch (`git checkout -b my-new-feature`)
27
93
  3. Commit your changes (`git commit -am 'Add some feature'`)
28
94
  4. Push to the branch (`git push origin my-new-feature`)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module StateMachines
2
4
  # Provides a set of higher-order features on top of the raw GraphViz graphs
3
5
  class Graph < GraphViz
@@ -23,12 +25,10 @@ module StateMachines
23
25
  # "landscape"). Default is "portrait".
24
26
  def initialize(name, options = {})
25
27
  options = { path: 'doc/state_machines', format: 'png', font: 'Arial', orientation: 'portrait' }.merge(options)
26
- options.assert_valid_keys(:path, :format, :font, :orientation)
28
+ StateMachines::OptionsValidator.assert_valid_keys!(options, :path, :format, :font, :orientation)
27
29
 
28
- # TODO fail if path cannot be created or readonly
29
- unless Dir.exist? options[:path]
30
- FileUtils.mkpath(options[:path])
31
- end
30
+ # TODO: fail if path cannot be created or readonly
31
+ FileUtils.mkpath(options[:path]) unless Dir.exist? options[:path]
32
32
  @font = options[:font]
33
33
  @file_path = File.join(options[:path], "#{name}.#{options[:format]}")
34
34
  @file_format = options[:format]
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'state_machines-diagram'
4
+
5
+ module StateMachines
6
+ module Graphviz
7
+ module Renderer
8
+ extend self
9
+
10
+ def draw_machine(machine, io: $stdout, **options)
11
+ diagram, builder = StateMachines::Diagram::Renderer.build_state_diagram(machine, options)
12
+ graph_options = options.dup
13
+ name = graph_options.delete(:name) || "#{machine.owner_class.name}_#{machine.name}"
14
+
15
+ draw_options = { human_name: false }
16
+ draw_options[:human_name] = graph_options.delete(:human_names) if graph_options.include?(:human_names)
17
+
18
+ graph = Graph.new(name, graph_options)
19
+ render_diagram(graph, diagram, builder, draw_options)
20
+ graph.output
21
+ graph
22
+ end
23
+
24
+ def draw_state(state, graph, options = {}, io = $stdout)
25
+ node = graph.add_nodes(
26
+ state.name ? state.name.to_s : 'nil',
27
+ label: state.description(options),
28
+ width: '1',
29
+ height: '1',
30
+ shape: state.final? ? 'doublecircle' : 'ellipse'
31
+ )
32
+
33
+ graph.add_edges(graph.add_nodes('starting_state', shape: 'point'), node) if state.initial?
34
+ true
35
+ end
36
+
37
+ def draw_event(event, graph, options = {}, io = $stdout)
38
+ machine = event.machine
39
+ valid_states = machine.states.by_priority.map(&:name)
40
+ event_label = options[:human_name] ? event.human_name(machine.owner_class) : event.name.to_s
41
+
42
+ event.branches.each do |branch|
43
+ draw_branch_with_label(branch, graph, event_label, machine, valid_states)
44
+ end
45
+
46
+ true
47
+ end
48
+
49
+ def draw_branch(branch, graph, event, valid_states, io = $stdout)
50
+ machine = event.machine
51
+ draw_branch_with_label(branch, graph, event.name.to_s, machine, valid_states)
52
+ true
53
+ end
54
+
55
+ private
56
+
57
+ def render_diagram(graph, diagram, builder, draw_options)
58
+ state_lookup = build_state_lookup(builder.machine)
59
+ state_metadata = builder&.state_metadata || {}
60
+ start_node = nil
61
+
62
+ diagram.states.each do |state_node|
63
+ metadata = state_metadata[state_node.id] || {}
64
+ state = state_lookup[state_node.id]
65
+ label = state ? state.description(human_name: draw_options[:human_name]) : (state_node.label || state_node.id)
66
+ shape = metadata[:type] == 'final' ? 'doublecircle' : 'ellipse'
67
+
68
+ node = graph.add_nodes(
69
+ graph_node_id(state_node.id),
70
+ label: label,
71
+ width: '1',
72
+ height: '1',
73
+ shape: shape
74
+ )
75
+
76
+ if metadata[:type] == 'initial'
77
+ start_node ||= graph.add_nodes('starting_state', shape: 'point')
78
+ graph.add_edges(start_node, node)
79
+ end
80
+ end
81
+
82
+ transition_metadata = transition_metadata_map(builder)
83
+ diagram.transitions.each do |transition|
84
+ metadata = transition_metadata[transition] || {}
85
+ callbacks = metadata[:callbacks] || {}
86
+
87
+ graph.add_edges(
88
+ graph_node_id(transition.source_state_id),
89
+ graph_node_id(transition.target_state_id),
90
+ label: transition.label.to_s,
91
+ labelfontsize: 10,
92
+ taillabel: Array(callbacks[:before]).compact.join("\n"),
93
+ headlabel: Array(callbacks[:after]).compact.join("\n")
94
+ )
95
+ end
96
+ end
97
+
98
+ def build_state_lookup(machine)
99
+ machine.states.each_with_object({}) do |state, memo|
100
+ key = state.name ? state.name.to_s : 'nil_state'
101
+ memo[key] = state
102
+ end
103
+ end
104
+
105
+ def graph_node_id(diagram_state_id)
106
+ diagram_state_id == 'nil_state' ? 'nil' : diagram_state_id
107
+ end
108
+
109
+ def transition_metadata_map(builder)
110
+ Array(builder&.transition_metadata).each_with_object({}) do |metadata, memo|
111
+ transition = metadata[:transition]
112
+ memo[transition] = metadata if transition
113
+ end
114
+ end
115
+
116
+ def draw_branch_with_label(branch, graph, event_label, machine, valid_states)
117
+ branch.state_requirements.each do |state_requirement|
118
+ from_states = state_requirement[:from].filter(valid_states)
119
+
120
+ if state_requirement[:to].values.empty?
121
+ loopback = true
122
+ else
123
+ to_state = state_requirement[:to].values.first
124
+ to_state = to_state ? to_state.to_s : 'nil'
125
+ loopback = false
126
+ end
127
+
128
+ from_states.each do |from_state|
129
+ from_state = from_state ? from_state.to_s : 'nil'
130
+ graph.add_edges(
131
+ from_state,
132
+ loopback ? from_state : to_state,
133
+ label: event_label,
134
+ labelfontsize: 10,
135
+ taillabel: callback_method_names(machine, branch, :before).join("\n"),
136
+ headlabel: callback_method_names(machine, branch, :after).join("\n")
137
+ )
138
+ end
139
+ end
140
+ end
141
+
142
+ def callback_method_names(machine, branch, type)
143
+ event_name = branch.event_requirement.values.first
144
+ machine.callbacks[type].select do |callback|
145
+ callback.branch.matches?(branch,
146
+ from: branch.state_requirements.map { |req| req[:from] },
147
+ to: branch.state_requirements.map { |req| req[:to] },
148
+ on: event_name)
149
+ end.flat_map do |callback|
150
+ callback.instance_variable_get('@methods')
151
+ end.compact
152
+ end
153
+ end
154
+ end
155
+ end
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module StateMachines
2
4
  # Gem version
3
5
  module Graphviz
4
- VERSION = '0.0.1'
6
+ VERSION = '0.1.0'
5
7
  end
6
8
  end
@@ -1,5 +1,39 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'state_machines'
2
4
  require 'graphviz'
3
- require 'state_machines/graphviz/monkeypatch'
4
5
  require 'state_machines/graphviz/graph'
6
+ require 'state_machines/graphviz/renderer'
5
7
  require 'state_machines/graphviz/version'
8
+
9
+ module StateMachines
10
+ module Graphviz
11
+ module_function
12
+
13
+ def draw(class_names, options = {})
14
+ if class_names.nil? || class_names.to_s.split(',').empty?
15
+ raise ArgumentError, 'At least one class must be specified'
16
+ end
17
+
18
+ # Load any files
19
+ if (files = options.delete(:file))
20
+ files.split(',').each { |file| require file }
21
+ end
22
+
23
+ class_names.to_s.split(',').each do |class_name|
24
+ # Navigate through the namespace structure to get to the class
25
+ klass = Object
26
+ class_name.split('::').each do |name|
27
+ klass = klass.const_defined?(name) ? klass.const_get(name) : klass.const_missing(name)
28
+ end
29
+
30
+ # Draw each of the class's state machines
31
+ klass.state_machines.each_value do |machine|
32
+ machine.draw(**options)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ require 'state_machines/tasks/railtie' if defined?(Rails)
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'state_machines/graphviz'
4
+ require 'rails'
5
+ module StateMachines
6
+ class Railtie < Rails::Railtie
7
+ rake_tasks do
8
+ load 'state_machines/tasks/state_machines.rake'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'state_machines/graphviz'
4
+
5
+ require File.join("#{File.dirname(__FILE__)}/state_machines")
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'state_machines-diagram'
4
+ require 'state_machines/graphviz'
5
+ require 'state_machines/graphviz/renderer'
6
+
7
+ # Set the renderer to use graphviz
8
+ StateMachines::Machine.renderer = StateMachines::Graphviz::Renderer
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Switch
2
4
  def self.name
3
5
  @name ||= "Switch_#{rand(1_000_000)}"
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require './test/test_helper'
4
+ describe StateMachines::Graph do
5
+ def setup
6
+ @graph = StateMachines::Graph.new('test')
7
+ end
8
+
9
+ def test_should_have_a_default_font
10
+ assert_equal('Arial', @graph.font)
11
+ end
12
+
13
+ def test_should_use_current_directory_for_filepath
14
+ assert_equal('doc/state_machines/test.png', @graph.file_path)
15
+ end
16
+
17
+ def test_should_have_a_default_file_format
18
+ assert_equal('png', @graph.file_format)
19
+ end
20
+
21
+ def test_should_have_a_default_orientation
22
+ assert_equal('TB', @graph[:rankdir].source)
23
+ end
24
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'test_helper'
4
+ describe StateMachines::Graph do
5
+ def setup
6
+ @graph = StateMachines::Graph.new('test')
7
+ @graph.add_nodes('parked', shape: 'ellipse')
8
+ @graph.add_nodes('idling', shape: 'ellipse')
9
+ @edge = @graph.add_edges('parked', 'idling', label: 'ignite')
10
+ end
11
+
12
+ def test_should_return_generated_edge
13
+ refute_nil(@edge)
14
+ end
15
+
16
+ def test_should_use_specified_nodes
17
+ assert_equal('parked', @edge.node_one(false))
18
+ assert_equal('idling', @edge.node_two(false))
19
+ end
20
+
21
+ def test_should_use_specified_options
22
+ assert_equal('ignite', @edge['label'].to_s.gsub('"', ''))
23
+ end
24
+
25
+ def test_should_set_default_font
26
+ assert_equal('Arial', @edge['fontname'].to_s.gsub('"', ''))
27
+ end
28
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'test_helper'
4
+ describe StateMachines::Graph do
5
+ def setup
6
+ @graph = StateMachines::Graph.new('test')
7
+ @node = @graph.add_nodes('parked', shape: 'ellipse')
8
+ end
9
+
10
+ def test_should_return_generated_node
11
+ refute_nil @node
12
+ end
13
+
14
+ def test_should_use_specified_name
15
+ assert_equal(@node, @graph.get_node('parked'))
16
+ end
17
+
18
+ def test_should_use_specified_options
19
+ assert_equal('ellipse', @node['shape'].to_s.gsub('"', ''))
20
+ end
21
+
22
+ def test_should_set_default_font
23
+ assert_equal('Arial', @node['fontname'].to_s.gsub('"', ''))
24
+ end
25
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'test_helper'
4
+ describe StateMachines::Graph do
5
+ def setup
6
+ @graph_name = "test_#{rand(1_000_000)}"
7
+ @graph = StateMachines::Graph.new(@graph_name)
8
+ @graph.add_nodes('parked', shape: 'ellipse')
9
+ @graph.add_nodes('idling', shape: 'ellipse')
10
+ @graph.add_edges('parked', 'idling', label: 'ignite')
11
+ @graph.output
12
+ end
13
+
14
+ def test_should_save_file
15
+ assert File.exist?("doc/state_machines/#{@graph_name}.png")
16
+ end
17
+
18
+ def teardown
19
+ FileUtils.rm Dir["doc/state_machines/#{@graph_name}.png"]
20
+ end
21
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'test_helper'
4
+
5
+ describe StateMachines::Machine do
6
+ def switch_file
7
+ File.expand_path("#{File.dirname(__FILE__)}/files/switch.rb")
8
+ end
9
+
10
+ def switch_machine
11
+ require switch_file
12
+ Switch.state_machines.values.first
13
+ end
14
+
15
+ def test_should_load_files
16
+ machine = switch_machine
17
+ assert(defined?(::Switch))
18
+ assert(machine)
19
+ end
20
+
21
+ def test_should_allow_path_and_format_to_be_customized
22
+ machine = switch_machine
23
+ output_path = File.dirname(__FILE__)
24
+ machine.draw(path: output_path, format: 'jpg')
25
+ assert(File.exist?("#{output_path}/#{Switch.name}_state.jpg"))
26
+ FileUtils.rm Dir["{.,#{output_path}}/#{Switch.name}_state.{jpg,png}"]
27
+ end
28
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'test_helper'
4
+
5
+ describe StateMachines::Graph do
6
+ def setup
7
+ @klass = Class.new do
8
+ def self.name
9
+ @name ||= "Vehicle_#{rand(1_000_000)}"
10
+ end
11
+ end
12
+
13
+ @machine = StateMachines::Machine.new(@klass, initial: :parked)
14
+ @machine.event :ignite do
15
+ transition parked: :idling
16
+ end
17
+ end
18
+
19
+ def test_should_raise_exception_if_invalid_option_specified
20
+ assert_raises(ArgumentError) { @machine.draw(invalid: true) }
21
+ end
22
+
23
+ def test_should_save_file_with_class_name_by_default
24
+ @machine.draw
25
+ assert File.exist?("doc/state_machines/#{@klass.name}_state.png"), 'Failed to save file with class name'
26
+ end
27
+
28
+ def test_should_allow_base_name_to_be_customized
29
+ name = "@machine_#{rand(1_000_000)}"
30
+ @machine.draw(name: name)
31
+ @path = "doc/state_@machines/#{name}.png"
32
+ end
33
+
34
+ def test_should_allow_format_to_be_customized
35
+ @machine.draw(format: 'jpg')
36
+ @path = "doc/state_machines/#{@klass.name}_state.jpg"
37
+ assert File.exist?(@path), 'allow format to be custom'
38
+ end
39
+
40
+ def test_should_allow_path_to_be_customized
41
+ @machine.draw(path: "#{File.dirname(__FILE__)}/")
42
+ @path = "#{File.dirname(__FILE__)}/#{@klass.name}_state.png"
43
+ assert(File.exist?(@path))
44
+ end
45
+
46
+ def test_should_allow_orientation_to_be_landscape
47
+ @graph = @machine.draw(orientation: 'landscape')
48
+ assert_equal(@graph['rankdir'].to_s.gsub('"', ''), 'LR')
49
+ end
50
+
51
+ def test_should_allow_orientation_to_be_portrait
52
+ @graph = @machine.draw(orientation: 'portrait')
53
+ assert_equal(@graph['rankdir'].to_s.gsub('"', ''), 'TB')
54
+ end
55
+
56
+ def test_should_allow_human_names_to_be_displayed
57
+ @machine.event :ignite, human_name: 'Ignite'
58
+ @machine.state :parked, human_name: 'Parked'
59
+ @machine.state :idling, human_name: 'Idling'
60
+ @graph = @machine.draw(human_names: true)
61
+
62
+ parked_node = @graph.get_node('parked')
63
+ assert_equal(parked_node['label'].to_s.gsub('"', ''), 'Parked')
64
+
65
+ idling_node = @graph.get_node('idling')
66
+ assert_equal(idling_node['label'].to_s.gsub('"', ''), 'Idling')
67
+ end
68
+
69
+ def teardown
70
+ FileUtils.rm Dir[@path || "doc/state_@machines/#{@klass.name}_state.png"]
71
+ end
72
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'test_helper'
4
+
5
+ describe StateMachines::Graph do
6
+ def setup
7
+ @klass = Class.new do
8
+ def self.name
9
+ @name ||= "Vehicle_#{rand(1_000_000)}"
10
+ end
11
+ end
12
+
13
+ @machine = StateMachines::Machine.new(@klass, initial: :parked)
14
+ @machine.event :activate do
15
+ transition parked: :idling
16
+ end
17
+
18
+ @machine.state :idling, value: -> { Time.now }
19
+
20
+ @graph = @machine.draw
21
+ end
22
+
23
+ def test_should_draw_all_states
24
+ assert(@graph.node_count, 3)
25
+ end
26
+
27
+ def test_should_draw_all_events
28
+ assert(@graph.edge_count, 2)
29
+ end
30
+
31
+ def test_should_draw_machine
32
+ assert(File.exist?("doc/state_machines/#{@klass.name}_state.png"))
33
+ end
34
+
35
+ def teardown
36
+ FileUtils.rm Dir["doc/state_machines/#{@klass.name}_state.png"]
37
+ end
38
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'test_helper'
4
+
5
+ describe StateMachines::Graph do
6
+ def setup
7
+ @klass = Class.new do
8
+ def self.name
9
+ @name ||= "Vehicle_#{rand(1_000_000)}"
10
+ end
11
+ end
12
+
13
+ @machine = StateMachines::Machine.new(@klass, :state_id, initial: :parked)
14
+ @machine.event :ignite do
15
+ transition parked: :idling
16
+ end
17
+ @machine.state :parked, value: 1
18
+ @machine.state :idling, value: 2
19
+
20
+ @graph = @machine.draw
21
+ end
22
+
23
+ def test_should_draw_all_states
24
+ assert_equal(@graph.node_count, 3)
25
+ end
26
+
27
+ def test_should_draw_all_events
28
+ assert_equal(@graph.edge_count, 2)
29
+ end
30
+
31
+ def test_should_draw_machine
32
+ assert(File.exist?("doc/state_machines/#{@klass.name}_state_id.png"))
33
+ end
34
+
35
+ def teardown
36
+ assert(File.exist?("doc/state_machines/#{@klass.name}_state_id.png"))
37
+ end
38
+ end