state_machines-graphviz 0.0.2 → 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 (42) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE.txt +1 -1
  3. data/README.md +14 -2
  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 +33 -1
  8. data/lib/state_machines/tasks/railtie.rb +3 -2
  9. data/lib/state_machines/tasks/state_machines.rake +2 -0
  10. data/lib/state_machines-graphviz.rb +7 -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 +61 -28
  30. data/.gitignore +0 -23
  31. data/.rspec +0 -3
  32. data/.travis.yml +0 -12
  33. data/Gemfile +0 -4
  34. data/Rakefile +0 -13
  35. data/lib/state_machines/graphviz/monkeypatch.rb +0 -161
  36. data/lib/state_machines/tasks/state_machines.rb +0 -32
  37. data/spec/graph_spec.rb +0 -92
  38. data/spec/machine_drawing_spec.rb +0 -215
  39. data/spec/machine_spec.rb +0 -19
  40. data/spec/spec_helper.rb +0 -2
  41. data/spec/state_spec.rb +0 -142
  42. data/state_machines-graphviz.gemspec +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 7816ed9a804fa1c1d96d1bb33ee7c06b8f46b6cc
4
- data.tar.gz: 9732fe05fdafb7b65cc69da7e9c9824a9d831262
2
+ SHA256:
3
+ metadata.gz: 90020c1871d32a31894619ff9883ae27e08210499cd4a1c0361b18ad503c04b1
4
+ data.tar.gz: 9f206b3af3589f1072c8514b182d57c066e1de9179effafb6351a63471f8d61f
5
5
  SHA512:
6
- metadata.gz: 9e2a02602e0ac22e1890d944ea9e4f24cd69cd7d6b428ad145fd01f359f4c893b4090a9af03da1d1635c288b28a13a29592d1983c7816dc6f9d3d83014edb981
7
- data.tar.gz: dbbbdf235db77bca214d0a5f21297da629d9d1868f86590d0c1173f4d304ab0258e4acdbc277035bce5a3813e50b92574104cbff94563ff58e18702b3107b344
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
@@ -19,6 +19,8 @@ Or install it yourself as:
19
19
 
20
20
  ## Usage
21
21
 
22
+ The default output folder for the images is `doc/state_machines`.
23
+
22
24
  #### Examples
23
25
 
24
26
  To generate a graph for a specific file / class:
@@ -67,16 +69,26 @@ in the class. The generated files will use an output filename of the format
67
69
  For examples of actual images generated using this task, see those under the
68
70
  examples folder.
69
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
+
70
82
  ### Interactive graphs
71
83
 
72
- Jean Bovet's [Visual Automata Simulator](http://www.cs.usfca.edu/~jbovet/vas.html)
84
+ Jean Bovet's [Visual Automata Simulator](https://github.com/NimaGhaedsharafi/VAS)
73
85
  is a great tool for "simulating, visualizing and transforming finite state
74
86
  automata and Turing Machines". It can help in the creation of states and events
75
87
  for your models. It is cross-platform, written in Java.
76
88
 
77
89
  ## Contributing
78
90
 
79
- 1. Fork it ( https://github.com/seuros/state_machines-graphviz/fork )
91
+ 1. Fork it ( https://github.com/state-machines/state_machines-graphviz/fork )
80
92
  2. Create your feature branch (`git checkout -b my-new-feature`)
81
93
  3. Commit your changes (`git commit -am 'Add some feature'`)
82
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.2'
6
+ VERSION = '0.1.0'
5
7
  end
6
8
  end
@@ -1,7 +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'
6
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
+
7
39
  require 'state_machines/tasks/railtie' if defined?(Rails)
@@ -1,10 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'state_machines/graphviz'
2
4
  require 'rails'
3
5
  module StateMachines
4
- # https://gist.github.com/josevalim/af7e572c2dc973add221
5
6
  class Railtie < Rails::Railtie
6
7
  rake_tasks do
7
8
  load 'state_machines/tasks/state_machines.rake'
8
9
  end
9
10
  end
10
- end
11
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'state_machines/graphviz'
2
4
 
3
5
  require File.join("#{File.dirname(__FILE__)}/state_machines")
@@ -1 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'state_machines-diagram'
1
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
@@ -0,0 +1,37 @@
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
+ @machine.state :parked, value: nil
18
+
19
+ @graph = @machine.draw
20
+ end
21
+
22
+ def test_should_draw_all_states
23
+ assert(@graph.node_count, 3)
24
+ end
25
+
26
+ def test_should_draw_all_events
27
+ assert(@graph.edge_count, 2)
28
+ end
29
+
30
+ def test_should_draw_machine
31
+ assert(File.exist?("doc/state_machines/#{@klass.name}_state.png"))
32
+ end
33
+
34
+ def teardown
35
+ assert(FileUtils.rm(Dir["doc/state_machines/#{@klass.name}_state.png"]))
36
+ end
37
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'test_helper'
4
+ describe StateMachines::Graph do
5
+ def setup
6
+ @machine = StateMachines::Machine.new(Class.new)
7
+ @machine.states << @state = StateMachines::State.new(@machine, :parked)
8
+
9
+ graph = StateMachines::Graph.new('test')
10
+ @state.draw(graph)
11
+ @node = graph.get_node('parked')
12
+ end
13
+
14
+ def test_should_use_doublecircle_as_shape
15
+ assert_equal('doublecircle', @node['shape'].to_s.gsub('"', ''))
16
+ end
17
+ end