transitions-reactflow 0.1.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 transitions-reactflow contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ include README.md
2
+ include LICENSE
3
+ include transitions_reactflow/*.pyi
4
+ include transitions_reactflow/py.typed
@@ -0,0 +1,222 @@
1
+ Metadata-Version: 2.4
2
+ Name: transitions-reactflow
3
+ Version: 0.1.0
4
+ Summary: React Flow graph engine for pytransitions state machines
5
+ Author: transitions_reactflow contributors
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 transitions-reactflow contributors
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/johnsonlm/transitions-reactflow
29
+ Project-URL: Bug Reports, https://github.com/johnsonlm/transitions-reactflow/issues
30
+ Project-URL: Source, https://github.com/johnsonlm/transitions-reactflow
31
+ Keywords: state-machine,transitions,visualization,react-flow
32
+ Classifier: Development Status :: 3 - Alpha
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: Programming Language :: Python :: 3
35
+ Classifier: Programming Language :: Python :: 3.8
36
+ Classifier: Programming Language :: Python :: 3.9
37
+ Classifier: Programming Language :: Python :: 3.10
38
+ Classifier: Programming Language :: Python :: 3.11
39
+ Classifier: Programming Language :: Python :: 3.12
40
+ Classifier: Programming Language :: Python :: 3.13
41
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
42
+ Classifier: Topic :: Scientific/Engineering :: Visualization
43
+ Requires-Python: >=3.8
44
+ Description-Content-Type: text/markdown
45
+ License-File: LICENSE
46
+ Requires-Dist: transitions>=0.8.0
47
+ Provides-Extra: dev
48
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
49
+ Requires-Dist: pytest-cov>=3.0.0; extra == "dev"
50
+ Requires-Dist: black>=22.0.0; extra == "dev"
51
+ Requires-Dist: mypy>=0.950; extra == "dev"
52
+ Requires-Dist: flake8>=4.0.0; extra == "dev"
53
+ Requires-Dist: build>=0.10.0; extra == "dev"
54
+ Requires-Dist: twine>=4.0.0; extra == "dev"
55
+ Dynamic: license-file
56
+
57
+ # transitions_reactflow
58
+
59
+ A React Flow graph engine extension for the [pytransitions](https://github.com/pytransitions/transitions) state machine library.
60
+
61
+ ## Features
62
+
63
+ - ✅ React Flow compatible graph data
64
+ - ✅ Hierarchical states with `children` parameter
65
+ - ✅ Smart filtering of unused states
66
+ - ✅ Unique edge IDs for duplicate transitions
67
+ - ✅ Full type hints with .pyi stub files
68
+ - ✅ Comprehensive test suite
69
+
70
+ ## Installation
71
+
72
+ ```bash
73
+ pip install transitions-reactflow
74
+ ```
75
+
76
+ Or install from source:
77
+
78
+ ```bash
79
+ git clone https://github.com/johnsonlm/transitions-reactflow
80
+ cd transitions-reactflow
81
+ pip install -e .
82
+ ```
83
+
84
+ ## Quick Start
85
+
86
+ ```python
87
+ from transitions_reactflow import ReactFlowMachine
88
+
89
+ # Define states with hierarchy
90
+ states = [
91
+ 'idle',
92
+ {'name': 'processing', 'children': ['validating', 'payment']},
93
+ 'completed'
94
+ ]
95
+
96
+ # Define transitions
97
+ transitions = [
98
+ {'trigger': 'start', 'source': 'idle', 'dest': 'processing_validating'},
99
+ {'trigger': 'validate', 'source': 'processing_validating', 'dest': 'processing_payment'},
100
+ {'trigger': 'finish', 'source': 'processing_payment', 'dest': 'completed'}
101
+ ]
102
+
103
+ # Create machine and get graph data
104
+ machine = ReactFlowMachine(states=states, transitions=transitions, initial='idle')
105
+ graph_data = machine.get_graph()
106
+
107
+ # Returns: {'nodes': [...], 'edges': [...]}
108
+ ```
109
+
110
+ ## Usage
111
+
112
+ ### With Flask Backend
113
+
114
+ ```python
115
+ from flask import Flask, jsonify
116
+ from flask_cors import CORS
117
+ from transitions_reactflow import ReactFlowMachine
118
+
119
+ app = Flask(__name__)
120
+ CORS(app) # Enable CORS for frontend requests
121
+
122
+ machine = ReactFlowMachine(states=states, transitions=transitions, initial='idle')
123
+
124
+ @app.route('/graph-data')
125
+ def get_graph_data():
126
+ return jsonify(machine.get_graph())
127
+ ```
128
+
129
+ ### With React Frontend
130
+
131
+ ```javascript
132
+ import ReactFlow from 'reactflow';
133
+
134
+ function StateMachineFlow() {
135
+ const [data, setData] = useState(null);
136
+
137
+ useEffect(() => {
138
+ fetch('/graph-data').then(res => res.json()).then(setData);
139
+ }, []);
140
+
141
+ return data && <ReactFlow nodes={data.nodes} edges={data.edges} fitView />;
142
+ }
143
+ ```
144
+
145
+ ## Hierarchical States
146
+
147
+ Use `children` for nested states:
148
+
149
+ ```python
150
+ states = [
151
+ 'idle',
152
+ {'name': 'error', 'children': ['validation', 'payment', 'network']}
153
+ ]
154
+ # Creates: error, error_validation, error_payment, error_network
155
+ ```
156
+
157
+ **Note**: Unused parent states are automatically filtered from the graph.
158
+
159
+ ## API
160
+
161
+ ### ReactFlowMachine
162
+
163
+ Extends `transitions.extensions.GraphMachine`.
164
+
165
+ - `get_graph(title=None, roi_state=None)` → `{'nodes': [...], 'edges': [...]}`
166
+ - `add_states(states)` - supports hierarchical definitions
167
+ - All standard pytransitions methods
168
+
169
+ ### ReactFlowGraph
170
+
171
+ Extends `transitions.extensions.diagrams_base.BaseGraph`.
172
+
173
+ - `get_graph(title=None, roi_state=None)` → React Flow data
174
+ - Standard BaseGraph methods
175
+
176
+ ## Graph Data Format
177
+
178
+ The `get_graph()` method returns React Flow compatible data:
179
+
180
+ **Nodes:**
181
+ ```python
182
+ {
183
+ "id": "state_name",
184
+ "data": {"label": "state_name"},
185
+ "position": {"x": 0, "y": 0}
186
+ }
187
+ ```
188
+
189
+ **Edges:**
190
+ ```python
191
+ {
192
+ "id": "e-source-target",
193
+ "source": "source_state",
194
+ "target": "target_state",
195
+ "label": "trigger_name"
196
+ }
197
+ ```
198
+
199
+ ## Development
200
+
201
+ ```bash
202
+ # Tests
203
+ pytest tests/
204
+
205
+ # Coverage
206
+ pytest --cov=transitions_reactflow tests/
207
+
208
+ # Type checking
209
+ mypy transitions_reactflow/
210
+ ```
211
+
212
+ ## License
213
+
214
+ MIT - see LICENSE file
215
+
216
+ ## Contributing
217
+
218
+ Contributions are welcome! Please feel free to submit a Pull Request.
219
+
220
+ ## Credits
221
+
222
+ Built on top of the [pytransitions](https://github.com/pytransitions/transitions) library.
@@ -0,0 +1,166 @@
1
+ # transitions_reactflow
2
+
3
+ A React Flow graph engine extension for the [pytransitions](https://github.com/pytransitions/transitions) state machine library.
4
+
5
+ ## Features
6
+
7
+ - ✅ React Flow compatible graph data
8
+ - ✅ Hierarchical states with `children` parameter
9
+ - ✅ Smart filtering of unused states
10
+ - ✅ Unique edge IDs for duplicate transitions
11
+ - ✅ Full type hints with .pyi stub files
12
+ - ✅ Comprehensive test suite
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pip install transitions-reactflow
18
+ ```
19
+
20
+ Or install from source:
21
+
22
+ ```bash
23
+ git clone https://github.com/johnsonlm/transitions-reactflow
24
+ cd transitions-reactflow
25
+ pip install -e .
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ```python
31
+ from transitions_reactflow import ReactFlowMachine
32
+
33
+ # Define states with hierarchy
34
+ states = [
35
+ 'idle',
36
+ {'name': 'processing', 'children': ['validating', 'payment']},
37
+ 'completed'
38
+ ]
39
+
40
+ # Define transitions
41
+ transitions = [
42
+ {'trigger': 'start', 'source': 'idle', 'dest': 'processing_validating'},
43
+ {'trigger': 'validate', 'source': 'processing_validating', 'dest': 'processing_payment'},
44
+ {'trigger': 'finish', 'source': 'processing_payment', 'dest': 'completed'}
45
+ ]
46
+
47
+ # Create machine and get graph data
48
+ machine = ReactFlowMachine(states=states, transitions=transitions, initial='idle')
49
+ graph_data = machine.get_graph()
50
+
51
+ # Returns: {'nodes': [...], 'edges': [...]}
52
+ ```
53
+
54
+ ## Usage
55
+
56
+ ### With Flask Backend
57
+
58
+ ```python
59
+ from flask import Flask, jsonify
60
+ from flask_cors import CORS
61
+ from transitions_reactflow import ReactFlowMachine
62
+
63
+ app = Flask(__name__)
64
+ CORS(app) # Enable CORS for frontend requests
65
+
66
+ machine = ReactFlowMachine(states=states, transitions=transitions, initial='idle')
67
+
68
+ @app.route('/graph-data')
69
+ def get_graph_data():
70
+ return jsonify(machine.get_graph())
71
+ ```
72
+
73
+ ### With React Frontend
74
+
75
+ ```javascript
76
+ import ReactFlow from 'reactflow';
77
+
78
+ function StateMachineFlow() {
79
+ const [data, setData] = useState(null);
80
+
81
+ useEffect(() => {
82
+ fetch('/graph-data').then(res => res.json()).then(setData);
83
+ }, []);
84
+
85
+ return data && <ReactFlow nodes={data.nodes} edges={data.edges} fitView />;
86
+ }
87
+ ```
88
+
89
+ ## Hierarchical States
90
+
91
+ Use `children` for nested states:
92
+
93
+ ```python
94
+ states = [
95
+ 'idle',
96
+ {'name': 'error', 'children': ['validation', 'payment', 'network']}
97
+ ]
98
+ # Creates: error, error_validation, error_payment, error_network
99
+ ```
100
+
101
+ **Note**: Unused parent states are automatically filtered from the graph.
102
+
103
+ ## API
104
+
105
+ ### ReactFlowMachine
106
+
107
+ Extends `transitions.extensions.GraphMachine`.
108
+
109
+ - `get_graph(title=None, roi_state=None)` → `{'nodes': [...], 'edges': [...]}`
110
+ - `add_states(states)` - supports hierarchical definitions
111
+ - All standard pytransitions methods
112
+
113
+ ### ReactFlowGraph
114
+
115
+ Extends `transitions.extensions.diagrams_base.BaseGraph`.
116
+
117
+ - `get_graph(title=None, roi_state=None)` → React Flow data
118
+ - Standard BaseGraph methods
119
+
120
+ ## Graph Data Format
121
+
122
+ The `get_graph()` method returns React Flow compatible data:
123
+
124
+ **Nodes:**
125
+ ```python
126
+ {
127
+ "id": "state_name",
128
+ "data": {"label": "state_name"},
129
+ "position": {"x": 0, "y": 0}
130
+ }
131
+ ```
132
+
133
+ **Edges:**
134
+ ```python
135
+ {
136
+ "id": "e-source-target",
137
+ "source": "source_state",
138
+ "target": "target_state",
139
+ "label": "trigger_name"
140
+ }
141
+ ```
142
+
143
+ ## Development
144
+
145
+ ```bash
146
+ # Tests
147
+ pytest tests/
148
+
149
+ # Coverage
150
+ pytest --cov=transitions_reactflow tests/
151
+
152
+ # Type checking
153
+ mypy transitions_reactflow/
154
+ ```
155
+
156
+ ## License
157
+
158
+ MIT - see LICENSE file
159
+
160
+ ## Contributing
161
+
162
+ Contributions are welcome! Please feel free to submit a Pull Request.
163
+
164
+ ## Credits
165
+
166
+ Built on top of the [pytransitions](https://github.com/pytransitions/transitions) library.
@@ -0,0 +1,63 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "transitions-reactflow"
7
+ version = "0.1.0"
8
+ description = "React Flow graph engine for pytransitions state machines"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = {file = "LICENSE"}
12
+ authors = [
13
+ {name = "transitions_reactflow contributors"}
14
+ ]
15
+ keywords = ["state-machine", "transitions", "visualization", "react-flow"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.8",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Topic :: Software Development :: Libraries :: Python Modules",
27
+ "Topic :: Scientific/Engineering :: Visualization",
28
+ ]
29
+ dependencies = [
30
+ "transitions>=0.8.0",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ dev = [
35
+ "pytest>=7.0.0",
36
+ "pytest-cov>=3.0.0",
37
+ "black>=22.0.0",
38
+ "mypy>=0.950",
39
+ "flake8>=4.0.0",
40
+ "build>=0.10.0",
41
+ "twine>=4.0.0",
42
+ ]
43
+
44
+ [project.urls]
45
+ Homepage = "https://github.com/johnsonlm/transitions-reactflow"
46
+ "Bug Reports" = "https://github.com/johnsonlm/transitions-reactflow/issues"
47
+ "Source" = "https://github.com/johnsonlm/transitions-reactflow"
48
+
49
+ [tool.setuptools]
50
+ packages = ["transitions_reactflow"]
51
+
52
+ [tool.setuptools.package-data]
53
+ transitions_reactflow = ["*.pyi", "py.typed"]
54
+
55
+ [tool.pytest.ini_options]
56
+ testpaths = ["tests"]
57
+ python_files = ["test_*.py"]
58
+
59
+ [tool.mypy]
60
+ python_version = "3.8"
61
+ warn_return_any = true
62
+ warn_unused_configs = true
63
+ disallow_untyped_defs = false
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,165 @@
1
+ """Tests for ReactFlowGraph class."""
2
+
3
+ import pytest
4
+ from transitions_reactflow import ReactFlowMachine
5
+
6
+
7
+ class TestReactFlowGraph:
8
+ """Test cases for ReactFlowGraph."""
9
+
10
+ def test_node_structure(self):
11
+ """Test that nodes have the correct structure."""
12
+ states = ['idle', 'running']
13
+ transitions = [{'trigger': 'start',
14
+ 'source': 'idle', 'dest': 'running'}]
15
+
16
+ machine = ReactFlowMachine(
17
+ states=states, transitions=transitions, initial='idle')
18
+ graph = machine.get_graph()
19
+
20
+ # Check node structure
21
+ for node in graph['nodes']:
22
+ assert 'id' in node
23
+ assert 'data' in node
24
+ assert 'position' in node
25
+ assert 'label' in node['data']
26
+ assert 'x' in node['position']
27
+ assert 'y' in node['position']
28
+
29
+ def test_edge_structure(self):
30
+ """Test that edges have the correct structure."""
31
+ states = ['idle', 'running']
32
+ transitions = [{'trigger': 'start',
33
+ 'source': 'idle', 'dest': 'running'}]
34
+
35
+ machine = ReactFlowMachine(
36
+ states=states, transitions=transitions, initial='idle')
37
+ graph = machine.get_graph()
38
+
39
+ # Check edge structure
40
+ for edge in graph['edges']:
41
+ assert 'id' in edge
42
+ assert 'source' in edge
43
+ assert 'target' in edge
44
+ assert 'label' in edge
45
+
46
+ def test_multiple_transitions(self):
47
+ """Test graph with multiple transitions."""
48
+ states = ['idle', 'validating', 'payment', 'completed']
49
+ transitions = [
50
+ {'trigger': 'start', 'source': 'idle', 'dest': 'validating'},
51
+ {'trigger': 'validate', 'source': 'validating', 'dest': 'payment'},
52
+ {'trigger': 'pay', 'source': 'payment', 'dest': 'completed'},
53
+ ]
54
+
55
+ machine = ReactFlowMachine(
56
+ states=states, transitions=transitions, initial='idle')
57
+ graph = machine.get_graph()
58
+
59
+ assert len(graph['nodes']) == 4
60
+ assert len(graph['edges']) == 3
61
+
62
+ def test_self_transition(self):
63
+ """Test graph with self-transition."""
64
+ states = ['idle', 'running']
65
+ transitions = [
66
+ {'trigger': 'start', 'source': 'idle', 'dest': 'running'},
67
+ {'trigger': 'retry', 'source': 'running',
68
+ 'dest': 'running'}, # Self-loop
69
+ ]
70
+
71
+ machine = ReactFlowMachine(
72
+ states=states, transitions=transitions, initial='idle')
73
+ graph = machine.get_graph()
74
+
75
+ # Find the self-loop edge
76
+ self_loop = [e for e in graph['edges'] if e['source'] == e['target']]
77
+ assert len(self_loop) == 1
78
+ assert self_loop[0]['source'] == 'running'
79
+ assert self_loop[0]['target'] == 'running'
80
+
81
+ def test_multiple_source_states(self):
82
+ """Test transition from multiple source states."""
83
+ states = ['idle', 'running', 'paused', 'stopped']
84
+ transitions = [
85
+ {'trigger': 'stop', 'source': [
86
+ 'running', 'paused'], 'dest': 'stopped'},
87
+ ]
88
+
89
+ machine = ReactFlowMachine(
90
+ states=states, transitions=transitions, initial='idle')
91
+ graph = machine.get_graph()
92
+
93
+ # Should create separate edges for each source
94
+ stop_edges = [e for e in graph['edges'] if e['label'] == 'stop']
95
+ assert len(stop_edges) == 2
96
+
97
+ sources = {e['source'] for e in stop_edges}
98
+ assert 'running' in sources
99
+ assert 'paused' in sources
100
+
101
+ def test_empty_graph(self):
102
+ """Test graph with no transitions."""
103
+ states = ['idle']
104
+ transitions = []
105
+
106
+ machine = ReactFlowMachine(
107
+ states=states, transitions=transitions, initial='idle')
108
+ graph = machine.get_graph()
109
+
110
+ # No transitions means no nodes (since unused states are filtered)
111
+ assert len(graph['nodes']) == 0
112
+ assert len(graph['edges']) == 0
113
+
114
+ def test_node_labels(self):
115
+ """Test that node labels default to state ID."""
116
+ states = ['idle', 'running']
117
+ transitions = [
118
+ {'trigger': 'start', 'source': 'idle', 'dest': 'running'}
119
+ ]
120
+
121
+ machine = ReactFlowMachine(
122
+ states=states, transitions=transitions, initial='idle')
123
+ graph = machine.get_graph()
124
+
125
+ # Check default labels (same as ID)
126
+ idle_node = [n for n in graph['nodes'] if n['id'] == 'idle'][0]
127
+ assert idle_node['data']['label'] == 'idle'
128
+
129
+ running_node = [n for n in graph['nodes'] if n['id'] == 'running'][0]
130
+ assert running_node['data']['label'] == 'running'
131
+
132
+ def test_roi_state_parameter(self):
133
+ """Test that roi_state parameter is accepted (though not implemented)."""
134
+ states = ['idle', 'running']
135
+ transitions = [{'trigger': 'start',
136
+ 'source': 'idle', 'dest': 'running'}]
137
+
138
+ machine = ReactFlowMachine(
139
+ states=states, transitions=transitions, initial='idle')
140
+ # The roi_state parameter is in the method signature but not used
141
+ # This test just ensures the method can be called
142
+ graph = machine.get_graph()
143
+ assert 'nodes' in graph
144
+ assert 'edges' in graph
145
+
146
+ def test_set_previous_transition(self):
147
+ """Test set_previous_transition method (no-op for React Flow)."""
148
+ from transitions_reactflow.diagrams_reactflow import ReactFlowGraph
149
+
150
+ graph = ReactFlowGraph(None)
151
+ # Should not raise an error
152
+ graph.set_previous_transition('idle', 'running')
153
+ # Method is no-op, so no assertions needed
154
+
155
+ def test_get_graph_exception_handling(self):
156
+ """Test exception handling in get_graph method."""
157
+ from transitions_reactflow.diagrams_reactflow import ReactFlowGraph
158
+ from unittest.mock import patch
159
+
160
+ graph = ReactFlowGraph(None)
161
+
162
+ # Mock _get_elements to return invalid data that causes an exception
163
+ with patch.object(graph, '_get_elements', return_value=(None, [])):
164
+ with pytest.raises(ValueError, match="Failed to generate React Flow graph"):
165
+ graph.get_graph()