py10x-universe 0.1.3__py3-none-any.whl

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 (214) hide show
  1. core_10x/__init__.py +42 -0
  2. core_10x/backbone/__init__.py +0 -0
  3. core_10x/backbone/backbone_store.py +59 -0
  4. core_10x/backbone/backbone_traitable.py +30 -0
  5. core_10x/backbone/backbone_user.py +66 -0
  6. core_10x/backbone/bound_data_domain.py +49 -0
  7. core_10x/backbone/namespace.py +101 -0
  8. core_10x/backbone/vault.py +38 -0
  9. core_10x/code_samples/__init__.py +0 -0
  10. core_10x/code_samples/_package_manifest.py +3 -0
  11. core_10x/code_samples/directories.py +181 -0
  12. core_10x/code_samples/person.py +76 -0
  13. core_10x/concrete_traits.py +356 -0
  14. core_10x/conftest.py +12 -0
  15. core_10x/curve.py +321 -0
  16. core_10x/data_domain.py +48 -0
  17. core_10x/data_domain_binder.py +45 -0
  18. core_10x/directory.py +250 -0
  19. core_10x/entity.py +8 -0
  20. core_10x/entity_filter.py +5 -0
  21. core_10x/environment_variables.py +147 -0
  22. core_10x/exec_control.py +84 -0
  23. core_10x/experimental/__init__.py +0 -0
  24. core_10x/experimental/data_protocol_ex.py +34 -0
  25. core_10x/global_cache.py +121 -0
  26. core_10x/manual_tests/__init__.py +0 -0
  27. core_10x/manual_tests/calendar_test.py +35 -0
  28. core_10x/manual_tests/ctor_update_bug.py +58 -0
  29. core_10x/manual_tests/debug_graph_on.py +17 -0
  30. core_10x/manual_tests/debug_graphoff_inside_graph_on.py +28 -0
  31. core_10x/manual_tests/enum_bits_test.py +17 -0
  32. core_10x/manual_tests/env_vars_trivial_test.py +12 -0
  33. core_10x/manual_tests/existing_traitable.py +33 -0
  34. core_10x/manual_tests/k10x_test1.py +13 -0
  35. core_10x/manual_tests/named_constant_test.py +121 -0
  36. core_10x/manual_tests/nucleus_trivial_test.py +42 -0
  37. core_10x/manual_tests/polars_test.py +14 -0
  38. core_10x/manual_tests/py_class_test.py +4 -0
  39. core_10x/manual_tests/rc_test.py +42 -0
  40. core_10x/manual_tests/rdate_test.py +12 -0
  41. core_10x/manual_tests/reference_serialization_bug.py +19 -0
  42. core_10x/manual_tests/resource_trivial_test.py +10 -0
  43. core_10x/manual_tests/store_uri_test.py +6 -0
  44. core_10x/manual_tests/trait_definition_test.py +19 -0
  45. core_10x/manual_tests/trait_filter_test.py +15 -0
  46. core_10x/manual_tests/trait_flag_modification_test.py +42 -0
  47. core_10x/manual_tests/trait_modification_bug.py +26 -0
  48. core_10x/manual_tests/traitable_as_of_test.py +82 -0
  49. core_10x/manual_tests/traitable_heir_test.py +39 -0
  50. core_10x/manual_tests/traitable_history_test.py +41 -0
  51. core_10x/manual_tests/traitable_serialization_test.py +54 -0
  52. core_10x/manual_tests/traitable_trivial_test.py +71 -0
  53. core_10x/manual_tests/trivial_graph_test.py +16 -0
  54. core_10x/manual_tests/ts_class_association_test.py +64 -0
  55. core_10x/manual_tests/ts_trivial_test.py +35 -0
  56. core_10x/named_constant.py +425 -0
  57. core_10x/nucleus.py +81 -0
  58. core_10x/package_manifest.py +85 -0
  59. core_10x/package_refactoring.py +153 -0
  60. core_10x/py_class.py +431 -0
  61. core_10x/rc.py +155 -0
  62. core_10x/rdate.py +339 -0
  63. core_10x/resource.py +189 -0
  64. core_10x/roman_number.py +67 -0
  65. core_10x/testlib/__init__.py +0 -0
  66. core_10x/testlib/test_store.py +240 -0
  67. core_10x/testlib/traitable_history_tests.py +787 -0
  68. core_10x/testlib/ts_tests.py +280 -0
  69. core_10x/trait.py +377 -0
  70. core_10x/trait_definition.py +176 -0
  71. core_10x/trait_filter.py +205 -0
  72. core_10x/trait_method_error.py +36 -0
  73. core_10x/traitable.py +1082 -0
  74. core_10x/traitable_cli.py +153 -0
  75. core_10x/traitable_heir.py +33 -0
  76. core_10x/traitable_id.py +31 -0
  77. core_10x/ts_store.py +172 -0
  78. core_10x/ts_store_type.py +26 -0
  79. core_10x/ts_union.py +147 -0
  80. core_10x/ui_hint.py +153 -0
  81. core_10x/unit_tests/test_concrete_traits.py +156 -0
  82. core_10x/unit_tests/test_converters.py +51 -0
  83. core_10x/unit_tests/test_curve.py +157 -0
  84. core_10x/unit_tests/test_directory.py +54 -0
  85. core_10x/unit_tests/test_documentation.py +172 -0
  86. core_10x/unit_tests/test_environment_variables.py +15 -0
  87. core_10x/unit_tests/test_filters.py +239 -0
  88. core_10x/unit_tests/test_graph.py +348 -0
  89. core_10x/unit_tests/test_named_constant.py +98 -0
  90. core_10x/unit_tests/test_rc.py +11 -0
  91. core_10x/unit_tests/test_rdate.py +484 -0
  92. core_10x/unit_tests/test_trait_method_error.py +80 -0
  93. core_10x/unit_tests/test_trait_modification.py +19 -0
  94. core_10x/unit_tests/test_traitable.py +959 -0
  95. core_10x/unit_tests/test_traitable_history.py +1 -0
  96. core_10x/unit_tests/test_ts_store.py +1 -0
  97. core_10x/unit_tests/test_ts_union.py +369 -0
  98. core_10x/unit_tests/test_ui_nodes.py +81 -0
  99. core_10x/unit_tests/test_xxcalendar.py +471 -0
  100. core_10x/vault/__init__.py +0 -0
  101. core_10x/vault/sec_keys.py +133 -0
  102. core_10x/vault/security_keys_old.py +168 -0
  103. core_10x/vault/vault.py +56 -0
  104. core_10x/vault/vault_traitable.py +56 -0
  105. core_10x/vault/vault_user.py +70 -0
  106. core_10x/xdate_time.py +136 -0
  107. core_10x/xnone.py +71 -0
  108. core_10x/xxcalendar.py +228 -0
  109. infra_10x/__init__.py +0 -0
  110. infra_10x/manual_tests/__init__.py +0 -0
  111. infra_10x/manual_tests/test_misc.py +16 -0
  112. infra_10x/manual_tests/test_prepare_filter_and_pipeline.py +25 -0
  113. infra_10x/mongodb_admin.py +111 -0
  114. infra_10x/mongodb_store.py +346 -0
  115. infra_10x/mongodb_utils.py +129 -0
  116. infra_10x/unit_tests/conftest.py +13 -0
  117. infra_10x/unit_tests/test_mongo_db.py +36 -0
  118. infra_10x/unit_tests/test_mongo_history.py +1 -0
  119. py10x_universe-0.1.3.dist-info/METADATA +406 -0
  120. py10x_universe-0.1.3.dist-info/RECORD +214 -0
  121. py10x_universe-0.1.3.dist-info/WHEEL +4 -0
  122. py10x_universe-0.1.3.dist-info/licenses/LICENSE +21 -0
  123. ui_10x/__init__.py +0 -0
  124. ui_10x/apps/__init__.py +0 -0
  125. ui_10x/apps/collection_editor_app.py +100 -0
  126. ui_10x/choice.py +212 -0
  127. ui_10x/collection_editor.py +135 -0
  128. ui_10x/concrete_trait_widgets.py +220 -0
  129. ui_10x/conftest.py +8 -0
  130. ui_10x/entity_stocker.py +173 -0
  131. ui_10x/examples/__init__.py +0 -0
  132. ui_10x/examples/_guess_word_data.py +14076 -0
  133. ui_10x/examples/collection_editor.py +17 -0
  134. ui_10x/examples/date_selector.py +14 -0
  135. ui_10x/examples/entity_stocker.py +18 -0
  136. ui_10x/examples/guess_word.py +392 -0
  137. ui_10x/examples/message_box.py +20 -0
  138. ui_10x/examples/multi_choice.py +17 -0
  139. ui_10x/examples/py_data_browser.py +66 -0
  140. ui_10x/examples/radiobox.py +29 -0
  141. ui_10x/examples/single_choice.py +31 -0
  142. ui_10x/examples/style_sheet.py +47 -0
  143. ui_10x/examples/trivial_entity_editor.py +18 -0
  144. ui_10x/platform.py +20 -0
  145. ui_10x/platform_interface.py +517 -0
  146. ui_10x/py_data_browser.py +249 -0
  147. ui_10x/qt6/__init__.py +0 -0
  148. ui_10x/qt6/conftest.py +8 -0
  149. ui_10x/qt6/manual_tests/__init__.py +0 -0
  150. ui_10x/qt6/manual_tests/basic_test.py +35 -0
  151. ui_10x/qt6/platform_implementation.py +275 -0
  152. ui_10x/qt6/utils.py +665 -0
  153. ui_10x/rio/__init__.py +0 -0
  154. ui_10x/rio/apps/examples/examples/__init__.py +22 -0
  155. ui_10x/rio/apps/examples/examples/components/__init__.py +3 -0
  156. ui_10x/rio/apps/examples/examples/components/collection_editor.py +15 -0
  157. ui_10x/rio/apps/examples/examples/pages/collection_editor.py +21 -0
  158. ui_10x/rio/apps/examples/examples/pages/login_page.py +88 -0
  159. ui_10x/rio/apps/examples/examples/pages/style_sheet.py +21 -0
  160. ui_10x/rio/apps/examples/rio.toml +14 -0
  161. ui_10x/rio/component_builder.py +497 -0
  162. ui_10x/rio/components/__init__.py +9 -0
  163. ui_10x/rio/components/group_box.py +31 -0
  164. ui_10x/rio/components/labeled_checkbox.py +18 -0
  165. ui_10x/rio/components/line_edit.py +37 -0
  166. ui_10x/rio/components/radio_button.py +32 -0
  167. ui_10x/rio/components/separator.py +24 -0
  168. ui_10x/rio/components/splitter.py +121 -0
  169. ui_10x/rio/components/tree_view.py +75 -0
  170. ui_10x/rio/conftest.py +35 -0
  171. ui_10x/rio/internals/__init__.py +0 -0
  172. ui_10x/rio/internals/app.py +192 -0
  173. ui_10x/rio/manual_tests/__init__.py +0 -0
  174. ui_10x/rio/manual_tests/basic_test.py +24 -0
  175. ui_10x/rio/manual_tests/splitter.py +27 -0
  176. ui_10x/rio/platform_implementation.py +91 -0
  177. ui_10x/rio/style_sheet.py +53 -0
  178. ui_10x/rio/unit_tests/test_collection_editor.py +68 -0
  179. ui_10x/rio/unit_tests/test_internals.py +630 -0
  180. ui_10x/rio/unit_tests/test_style_sheet.py +37 -0
  181. ui_10x/rio/widgets/__init__.py +46 -0
  182. ui_10x/rio/widgets/application.py +109 -0
  183. ui_10x/rio/widgets/button.py +48 -0
  184. ui_10x/rio/widgets/button_group.py +60 -0
  185. ui_10x/rio/widgets/calendar.py +23 -0
  186. ui_10x/rio/widgets/checkbox.py +24 -0
  187. ui_10x/rio/widgets/dialog.py +137 -0
  188. ui_10x/rio/widgets/group_box.py +27 -0
  189. ui_10x/rio/widgets/layout.py +34 -0
  190. ui_10x/rio/widgets/line_edit.py +37 -0
  191. ui_10x/rio/widgets/list.py +105 -0
  192. ui_10x/rio/widgets/message_box.py +70 -0
  193. ui_10x/rio/widgets/scroll_area.py +31 -0
  194. ui_10x/rio/widgets/spacer.py +6 -0
  195. ui_10x/rio/widgets/splitter.py +45 -0
  196. ui_10x/rio/widgets/text_edit.py +28 -0
  197. ui_10x/rio/widgets/tree.py +89 -0
  198. ui_10x/rio/widgets/unit_tests/test_button.py +101 -0
  199. ui_10x/rio/widgets/unit_tests/test_button_group.py +33 -0
  200. ui_10x/rio/widgets/unit_tests/test_calendar.py +114 -0
  201. ui_10x/rio/widgets/unit_tests/test_checkbox.py +109 -0
  202. ui_10x/rio/widgets/unit_tests/test_group_box.py +158 -0
  203. ui_10x/rio/widgets/unit_tests/test_label.py +43 -0
  204. ui_10x/rio/widgets/unit_tests/test_line_edit.py +140 -0
  205. ui_10x/rio/widgets/unit_tests/test_list.py +146 -0
  206. ui_10x/table_header_view.py +305 -0
  207. ui_10x/table_view.py +174 -0
  208. ui_10x/trait_editor.py +189 -0
  209. ui_10x/trait_widget.py +131 -0
  210. ui_10x/traitable_editor.py +200 -0
  211. ui_10x/traitable_view.py +131 -0
  212. ui_10x/unit_tests/conftest.py +8 -0
  213. ui_10x/unit_tests/test_platform.py +9 -0
  214. ui_10x/utils.py +661 -0
@@ -0,0 +1,630 @@
1
+ """
2
+ Tests for ui_10x.rio.internals module.
3
+
4
+ This module tests the App10x, FastapiServer, and Session classes that provide
5
+ custom Rio app functionality for running in webview windows.
6
+ """
7
+
8
+ import pathlib
9
+ import sys
10
+ from unittest.mock import Mock, patch
11
+
12
+ import pytest
13
+ from ui_10x.rio.internals.app import App10x, FastapiServer, Session, app_server
14
+
15
+ import rio
16
+
17
+
18
+ class TestApp10x:
19
+ """Test cases for the App10x class."""
20
+
21
+ def test_app10x_initialization(self):
22
+ """Test App10x initialization with default values."""
23
+ mock_app = Mock()
24
+ app10x = App10x(app=mock_app)
25
+
26
+ assert app10x.app is mock_app
27
+ assert app10x.webview is None
28
+
29
+ def test_app10x_initialization_with_webview(self):
30
+ """Test App10x initialization with webview provided."""
31
+ mock_app = Mock()
32
+ mock_webview = Mock()
33
+ app10x = App10x(app=mock_app, webview=mock_webview)
34
+
35
+ assert app10x.app is mock_app
36
+ assert app10x.webview is mock_webview
37
+
38
+ def test_update_window_size_no_dimensions(self, monkeypatch):
39
+ """Test _update_window_size when no dimensions are provided."""
40
+ # Mock the webview module that gets imported inside the method
41
+ mock_webview = Mock()
42
+ mock_webview.windows = [Mock()]
43
+ monkeypatch.setitem(sys.modules, 'webview', mock_webview)
44
+
45
+ App10x._update_window_size(None, None)
46
+ # Should return early without calling webview methods
47
+ # Since webview is imported inside the method, we can't easily test the early return
48
+ # without more complex mocking, so we'll just verify the method doesn't crash
49
+ pass
50
+
51
+ def test_update_window_size_with_dimensions(self, monkeypatch):
52
+ """Test _update_window_size when dimensions are provided."""
53
+ mock_webview = Mock()
54
+ mock_window = Mock()
55
+ mock_window.width = 800
56
+ mock_window.height = 600
57
+ mock_window.evaluate_js.return_value = 16.0 # pixels_per_rem
58
+ mock_webview.windows = [mock_window]
59
+
60
+ monkeypatch.setitem(sys.modules, 'webview', mock_webview)
61
+
62
+ App10x._update_window_size(50.0, 40.0)
63
+
64
+ # Verify evaluate_js was called to get pixels_per_rem
65
+ mock_window.evaluate_js.assert_called_once()
66
+ # Verify resize was called with calculated pixel dimensions
67
+ mock_window.resize.assert_called_once_with(800, 640) # 50*16, 40*16
68
+
69
+ def test_update_window_size_partial_dimensions(self, monkeypatch):
70
+ """Test _update_window_size with only width or height provided."""
71
+ mock_webview = Mock()
72
+ mock_window = Mock()
73
+ mock_window.width = 800
74
+ mock_window.height = 600
75
+ mock_window.evaluate_js.return_value = 16.0
76
+ mock_webview.windows = [mock_window]
77
+
78
+ monkeypatch.setitem(sys.modules, 'webview', mock_webview)
79
+
80
+ # Test with only width
81
+ App10x._update_window_size(50.0, None)
82
+ mock_window.resize.assert_called_with(800, 600) # width calculated, height unchanged
83
+
84
+ # Reset for next test
85
+ mock_window.reset_mock()
86
+ mock_window.evaluate_js.return_value = 16.0
87
+
88
+ # Test with only height
89
+ App10x._update_window_size(None, 40.0)
90
+ mock_window.resize.assert_called_with(800, 640) # height calculated, width unchanged
91
+
92
+ @patch('ui_10x.rio.internals.app.WebViewProcess')
93
+ @patch('ui_10x.rio.internals.app.utils.ensure_valid_port')
94
+ @patch('asyncio.run')
95
+ def test_run_in_window_basic(self, mock_asyncio_run, mock_ensure_port, mock_webview_process):
96
+ """Test basic _run_in_window functionality."""
97
+ # Setup mocks
98
+ mock_ensure_port.return_value = 8080
99
+ mock_asyncio_run.return_value = '/path/to/icon.png'
100
+ mock_webview = Mock()
101
+ mock_webview.is_alive.return_value = True
102
+ mock_webview_process.return_value = mock_webview
103
+
104
+ mock_app = Mock()
105
+ mock_app.name = 'Test App'
106
+ app10x = App10x(app=mock_app)
107
+
108
+ # Mock the _run_as_web_server method
109
+ mock_app._run_as_web_server = Mock()
110
+
111
+ app10x._run_in_window()
112
+
113
+ # Verify WebViewProcess was created with correct parameters
114
+ mock_webview_process.assert_called_once()
115
+ call_kwargs = mock_webview_process.call_args[1]
116
+ assert call_kwargs['url'] == 'http://localhost:8080'
117
+ assert call_kwargs['title'] == 'Test App'
118
+ assert call_kwargs['maximized'] is False
119
+ assert call_kwargs['fullscreen'] is False
120
+
121
+ # Verify _run_as_web_server was called
122
+ mock_app._run_as_web_server.assert_called_once()
123
+ call_kwargs = mock_app._run_as_web_server.call_args[1]
124
+ assert call_kwargs['host'] == 'localhost'
125
+ assert call_kwargs['port'] == 8080
126
+ assert call_kwargs['running_in_window'] is True
127
+ assert 'internal_on_server_created' in call_kwargs
128
+
129
+ @patch('ui_10x.rio.internals.app.WebViewProcess')
130
+ @patch('ui_10x.rio.internals.app.utils.ensure_valid_port')
131
+ @patch('asyncio.run')
132
+ def test_run_in_window_with_options(self, mock_asyncio_run, mock_ensure_port, mock_webview_process):
133
+ """Test _run_in_window with various options."""
134
+ mock_ensure_port.return_value = 8080
135
+ mock_asyncio_run.return_value = '/path/to/icon.png'
136
+ mock_webview = Mock()
137
+ mock_webview.is_alive.return_value = True
138
+ mock_webview_process.return_value = mock_webview
139
+
140
+ mock_app = Mock()
141
+ mock_app.name = 'Test App'
142
+ app10x = App10x(app=mock_app)
143
+ mock_app._run_as_web_server = Mock()
144
+
145
+ # Test with custom options
146
+ app10x._run_in_window(quiet=False, maximized=True, fullscreen=True, width=100.0, height=80.0, debug_mode=True)
147
+
148
+ # Verify WebViewProcess was created with custom options
149
+ call_kwargs = mock_webview_process.call_args[1]
150
+ assert call_kwargs['maximized'] is True
151
+ assert call_kwargs['fullscreen'] is True
152
+
153
+ # Verify _run_as_web_server was called with custom options
154
+ call_kwargs = mock_app._run_as_web_server.call_args[1]
155
+ assert call_kwargs['quiet'] is False
156
+ assert call_kwargs['debug_mode'] is True
157
+
158
+ @patch('ui_10x.rio.internals.app.WebViewProcess')
159
+ @patch('ui_10x.rio.internals.app.utils.ensure_valid_port')
160
+ @patch('asyncio.run')
161
+ def test_run_in_window_exception_handling(self, mock_asyncio_run, mock_ensure_port, mock_webview_process):
162
+ """Test _run_in_window exception handling and cleanup."""
163
+ mock_ensure_port.return_value = 8080
164
+ mock_asyncio_run.return_value = '/path/to/icon.png'
165
+ mock_webview = Mock()
166
+ mock_webview.is_alive.return_value = True
167
+ mock_webview_process.return_value = mock_webview
168
+
169
+ mock_app = Mock()
170
+ mock_app.name = 'Test App'
171
+ mock_app._run_as_web_server.side_effect = Exception('Test error')
172
+ app10x = App10x(app=mock_app)
173
+
174
+ # Should not raise exception
175
+ app10x._run_in_window()
176
+
177
+ # Verify cleanup was performed
178
+ mock_webview.close.assert_called_once()
179
+ mock_webview.join.assert_called_once()
180
+
181
+ @patch('ui_10x.rio.internals.app.WebViewProcess')
182
+ @patch('ui_10x.rio.internals.app.utils.ensure_valid_port')
183
+ @patch('asyncio.run')
184
+ def test_run_in_window_on_server_created_callback(self, mock_asyncio_run, mock_ensure_port, mock_webview_process):
185
+ """Test _on_server_created callback functionality."""
186
+ # Setup mocks
187
+ mock_ensure_port.return_value = 8080
188
+ mock_asyncio_run.return_value = '/path/to/icon.png'
189
+ mock_webview = Mock()
190
+ mock_webview.is_alive.return_value = True
191
+ mock_webview_process.return_value = mock_webview
192
+
193
+ mock_app = Mock()
194
+ mock_app.name = 'Test App'
195
+ app10x = App10x(app=mock_app)
196
+
197
+ # Mock the _run_as_web_server method to capture the callback
198
+ mock_app._run_as_web_server = Mock()
199
+
200
+ # Custom callback to test
201
+ callback_called = []
202
+
203
+ def test_callback(server):
204
+ callback_called.append(server)
205
+
206
+ app10x._run_in_window(on_server_created=test_callback)
207
+
208
+ # Get the internal_on_server_created callback
209
+ call_kwargs = mock_app._run_as_web_server.call_args[1]
210
+ internal_callback = call_kwargs['internal_on_server_created']
211
+
212
+ # Test the callback
213
+ mock_server = Mock()
214
+ mock_server.config = Mock()
215
+ mock_server.config.app = Mock()
216
+ internal_callback(mock_server)
217
+
218
+ # Verify the callback was called
219
+ assert len(callback_called) == 1
220
+ assert callback_called[0] is mock_server
221
+
222
+ # Verify server was configured
223
+ assert hasattr(mock_server.config.app, 'app10x')
224
+ assert mock_server.config.app.app10x is app10x
225
+
226
+
227
+ class TestFastapiServer:
228
+ """Test cases for the FastapiServer class."""
229
+
230
+ def test_fastapi_server_class_attributes(self):
231
+ """Test FastapiServer class attributes."""
232
+ # Test that FastapiServer has the expected type annotation
233
+ # The app10x is defined as a type annotation, not a class attribute
234
+ # We can check that the class has the annotation in its __annotations__
235
+ assert 'app10x' in FastapiServer.__annotations__
236
+ # The annotation is stored as a string, so we check for the string representation
237
+ assert FastapiServer.__annotations__['app10x'] == 'App10x'
238
+
239
+ def test_fastapi_server_inheritance(self):
240
+ """Test that FastapiServer properly inherits from app_server.FastapiServer."""
241
+ from ui_10x.rio.internals.app import app_server
242
+
243
+ assert issubclass(FastapiServer, app_server.FastapiServer)
244
+
245
+ async def test_fastapi_server_create_session(self):
246
+ """Test create_session method creates Session with app10x attribute."""
247
+ # Create a mock server that behaves like FastapiServer
248
+ server = Mock()
249
+ server.__class__ = FastapiServer
250
+ server.app10x = Mock()
251
+
252
+ # Mock the parent create_session method
253
+ mock_session = Mock(spec=rio.Session)
254
+ mock_parent_create_session = Mock()
255
+
256
+ async def fake_create_session(*args, **kwargs):
257
+ mock_parent_create_session(*args, **kwargs)
258
+ return mock_session
259
+
260
+ with patch.object(app_server.FastapiServer, 'create_session', fake_create_session):
261
+ # Call the actual FastapiServer.create_session method
262
+ result = await FastapiServer.create_session(server)
263
+
264
+ # Should call parent method
265
+ mock_parent_create_session.assert_called_once()
266
+
267
+ # Should return a Session instance with app10x attribute
268
+ assert result.__class__ == Session
269
+ assert result.app10x is server.app10x
270
+
271
+
272
+ class TestSession:
273
+ """Test cases for the Session class."""
274
+
275
+ def test_session_class_attributes(self):
276
+ """Test Session class attributes."""
277
+ # Test that Session has the expected class attributes
278
+ assert hasattr(Session, 'app10x')
279
+ # The app10x attribute should be a class attribute that can be set on instances
280
+ assert isinstance(Session.app10x, type(None)) or hasattr(Session, 'app10x')
281
+
282
+ def test_session_inheritance(self):
283
+ """Test that Session properly inherits from rio.Session."""
284
+ assert issubclass(Session, rio.Session)
285
+
286
+ def test_session_method_attributes(self):
287
+ """Test that Session has the correct method attributes."""
288
+ # Verify that Session has the expected method attributes
289
+ assert hasattr(Session, '_local_methods_')
290
+ assert hasattr(Session, '_remote_methods_')
291
+
292
+ # These should be copies of the parent class attributes
293
+ assert Session._local_methods_ is not rio.Session._local_methods_
294
+ assert Session._remote_methods_ is not rio.Session._remote_methods_
295
+ assert isinstance(Session._local_methods_, dict)
296
+ assert isinstance(Session._remote_methods_, dict)
297
+
298
+ # The content should be the same as the parent class
299
+ assert Session._local_methods_ == rio.Session._local_methods_
300
+ assert Session._remote_methods_ == rio.Session._remote_methods_
301
+
302
+ def test_session_methods_exist(self):
303
+ """Test that Session has the expected methods."""
304
+ # Test that the custom methods exist on the Session class
305
+ assert hasattr(Session, '_close')
306
+ assert hasattr(Session, '_get_webview_window')
307
+ assert hasattr(Session, 'set_title')
308
+ assert hasattr(Session, 'pick_folder')
309
+ assert hasattr(Session, 'pick_file')
310
+ assert hasattr(Session, 'save_file')
311
+
312
+ async def test_session_close_not_running_in_window(self):
313
+ """Test _close when not running in window calls parent method twice."""
314
+ # Create a mock session with the actual Session class behavior
315
+ session = Mock()
316
+ session.__class__ = Session
317
+ session.running_in_window = False
318
+
319
+ # Mock the parent _close method
320
+ mock_parent_close = Mock()
321
+
322
+ async def fake_close(*args, **kwargs):
323
+ mock_parent_close(*args, **kwargs)
324
+
325
+ with patch.object(rio.Session, '_close', fake_close):
326
+ # Call the actual Session._close method
327
+ await Session._close(session, close_remote_session=True)
328
+
329
+ # Should call parent method twice: once with original parameter, once with False
330
+ assert mock_parent_close.call_count == 2
331
+ mock_parent_close.assert_any_call(session, close_remote_session=True)
332
+ mock_parent_close.assert_any_call(session, close_remote_session=False)
333
+
334
+ async def test_session_close_running_in_window(self):
335
+ """Test _close when running in window calls parent with False and closes webview."""
336
+ session = Mock()
337
+ session.__class__ = Session
338
+ session.running_in_window = True
339
+ session.app10x = Mock()
340
+ session.app10x.webview = Mock()
341
+
342
+ # Mock the parent _close method
343
+ mock_parent_close = Mock()
344
+
345
+ async def fake_close(*args, **kwargs):
346
+ mock_parent_close(*args, **kwargs)
347
+
348
+ with patch.object(rio.Session, '_close', fake_close):
349
+ # Call the actual Session._close method
350
+ await Session._close(session, close_remote_session=True)
351
+
352
+ # Should call parent method once with close_remote_session=False
353
+ mock_parent_close.assert_called_once_with(session, close_remote_session=False)
354
+
355
+ # Should close webview
356
+ session.app10x.webview.close.assert_called_once()
357
+
358
+ async def test_session_get_webview_window_raises_error(self):
359
+ """Test _get_webview_window raises RuntimeError."""
360
+ session = Mock()
361
+ session.__class__ = Session
362
+
363
+ with pytest.raises(RuntimeError, match='Should not be called required in out-of-process webview'):
364
+ await Session._get_webview_window(session)
365
+
366
+ async def test_session_set_title_not_running_in_window(self):
367
+ """Test set_title when not running in window calls parent method."""
368
+ session = Mock()
369
+ session.__class__ = Session
370
+ session.running_in_window = False
371
+
372
+ # Mock the parent set_title method
373
+ mock_parent_set_title = Mock()
374
+
375
+ async def fake_set_title(*args, **kwargs):
376
+ mock_parent_set_title(*args, **kwargs)
377
+
378
+ with patch.object(rio.Session, 'set_title', fake_set_title):
379
+ # Call the actual Session.set_title method
380
+ await Session.set_title(session, 'Test Title')
381
+
382
+ # Should call parent method
383
+ mock_parent_set_title.assert_called_once_with(session, 'Test Title')
384
+
385
+ async def test_session_set_title_running_in_window(self):
386
+ """Test set_title when running in window calls webview instead of parent."""
387
+ session = Mock()
388
+ session.__class__ = Session
389
+ session.running_in_window = True
390
+ session.app10x = Mock()
391
+ session.app10x.webview = Mock()
392
+
393
+ # Mock the parent set_title method
394
+ mock_parent_set_title = Mock()
395
+
396
+ async def fake_set_title(*args, **kwargs):
397
+ mock_parent_set_title(*args, **kwargs)
398
+
399
+ with patch.object(rio.Session, 'set_title', fake_set_title):
400
+ # Call the actual Session.set_title method
401
+ await Session.set_title(session, 'Test Title')
402
+
403
+ # Should not call parent method
404
+ mock_parent_set_title.assert_not_called()
405
+
406
+ # Should call webview set_title
407
+ session.app10x.webview.set_title.assert_called_once_with('Test Title')
408
+
409
+ async def test_session_pick_folder_not_running_in_window(self):
410
+ """Test pick_folder when not running in window calls parent method."""
411
+ session = Mock()
412
+ session.__class__ = Session
413
+ session.running_in_window = False
414
+ expected_path = pathlib.Path('/test/path')
415
+
416
+ # Mock the parent pick_folder method
417
+ mock_parent_pick_folder = Mock()
418
+
419
+ async def fake_pick_folder(*args, **kwargs):
420
+ mock_parent_pick_folder(*args, **kwargs)
421
+ return expected_path
422
+
423
+ with patch.object(rio.Session, 'pick_folder', fake_pick_folder):
424
+ # Call the actual Session.pick_folder method
425
+ result = await Session.pick_folder(session)
426
+
427
+ # Should call parent method and return its result
428
+ mock_parent_pick_folder.assert_called_once()
429
+ assert result == expected_path
430
+
431
+ async def test_session_pick_folder_running_in_window(self):
432
+ """Test pick_folder when running in window calls webview and returns Path."""
433
+ session = Mock()
434
+ session.running_in_window = True
435
+ session.app10x = Mock()
436
+ session.app10x.webview = Mock()
437
+ session.app10x.webview.pick_folder.return_value = '/test/path'
438
+
439
+ # Call the actual Session.pick_folder method
440
+ result = await Session.pick_folder(session)
441
+
442
+ # Should call webview pick_folder and return Path
443
+ session.app10x.webview.pick_folder.assert_called_once()
444
+ assert result == pathlib.Path('/test/path')
445
+
446
+ async def test_session_pick_file_not_running_in_window(self):
447
+ """Test pick_file when not running in window calls parent method."""
448
+ session = Mock()
449
+ session.__class__ = Session
450
+ session.running_in_window = False
451
+ expected_file_info = Mock()
452
+
453
+ # Mock the parent pick_file method
454
+ mock_parent_pick_file = Mock()
455
+
456
+ async def fake_pick_file(*args, **kwargs):
457
+ mock_parent_pick_file(*args, **kwargs)
458
+ return expected_file_info
459
+
460
+ with patch.object(rio.Session, 'pick_file', fake_pick_file):
461
+ # Call the actual Session.pick_file method
462
+ result = await Session.pick_file(session, file_types=['txt'], multiple=False)
463
+
464
+ # Should call parent method and return its result
465
+ mock_parent_pick_file.assert_called_once_with(session, file_types=['txt'], multiple=False)
466
+ assert result == expected_file_info
467
+
468
+ async def test_session_pick_file_running_in_window_single(self):
469
+ """Test pick_file when running in window (single file) calls webview."""
470
+ session = Mock()
471
+ session.running_in_window = True
472
+ session.app10x = Mock()
473
+ session.app10x.webview = Mock()
474
+ session.app10x.webview.pick_file.return_value = '/test/file.txt'
475
+
476
+ # Mock utils.FileInfo._from_path
477
+ mock_file_info = Mock()
478
+ with patch('ui_10x.rio.internals.app.utils.FileInfo._from_path', return_value=mock_file_info):
479
+ # Call the actual Session.pick_file method
480
+ result = await Session.pick_file(session, file_types=['txt'], multiple=False)
481
+
482
+ # Should call webview pick_file with normalized file types
483
+ session.app10x.webview.pick_file.assert_called_once_with(file_types=['txt (*.txt)'], multiple=False)
484
+ assert result == mock_file_info
485
+
486
+ async def test_session_pick_file_running_in_window_multiple(self):
487
+ """Test pick_file when running in window (multiple files) calls webview."""
488
+ session = Mock()
489
+ session.running_in_window = True
490
+ session.app10x = Mock()
491
+ session.app10x.webview = Mock()
492
+ session.app10x.webview.pick_file.return_value = ['/test/file1.txt', '/test/file2.txt']
493
+
494
+ # Mock utils.FileInfo._from_path
495
+ mock_file_info1 = Mock()
496
+ mock_file_info2 = Mock()
497
+ with patch('ui_10x.rio.internals.app.utils.FileInfo._from_path', side_effect=[mock_file_info1, mock_file_info2]):
498
+ # Call the actual Session.pick_file method
499
+ result = await Session.pick_file(session, file_types=['txt'], multiple=True)
500
+
501
+ # Should call webview pick_file with normalized file types
502
+ session.app10x.webview.pick_file.assert_called_once_with(file_types=['txt (*.txt)'], multiple=True)
503
+ assert result == [mock_file_info1, mock_file_info2]
504
+
505
+ async def test_session_pick_file_no_selection(self):
506
+ """Test pick_file when no file is selected raises NoFileSelectedError."""
507
+ session = Mock()
508
+ session.running_in_window = True
509
+ session.app10x = Mock()
510
+ session.app10x.webview = Mock()
511
+ session.app10x.webview.pick_file.return_value = None
512
+
513
+ # Should raise NoFileSelectedError
514
+ with pytest.raises(rio.errors.NoFileSelectedError):
515
+ await Session.pick_file(session, file_types=['txt'], multiple=False)
516
+
517
+ async def test_session_pick_file_normalize_file_types(self):
518
+ """Test pick_file normalizes and deduplicates file types."""
519
+ session = Mock()
520
+ session.running_in_window = True
521
+ session.app10x = Mock()
522
+ session.app10x.webview = Mock()
523
+ session.app10x.webview.pick_file.return_value = '/test/file.txt'
524
+
525
+ mock_file_info = Mock()
526
+ with patch('ui_10x.rio.internals.app.utils.FileInfo._from_path', return_value=mock_file_info):
527
+ # Call the actual Session.pick_file method
528
+ await Session.pick_file(session, file_types=['txt', 'pdf'], multiple=False)
529
+
530
+ # Should call webview with file types formatted correctly
531
+ session.app10x.webview.pick_file.assert_called_once_with(file_types=['txt (*.txt)', 'pdf (*.pdf)'], multiple=False)
532
+
533
+ async def test_session_save_file_not_running_in_window(self):
534
+ """Test save_file when not running in window calls parent method."""
535
+ session = Mock()
536
+ session.__class__ = Session
537
+ session.running_in_window = False
538
+
539
+ # Mock the parent save_file method
540
+ mock_parent_save_file = Mock()
541
+
542
+ async def fake_save_file(*args, **kwargs):
543
+ mock_parent_save_file(*args, **kwargs)
544
+
545
+ with patch.object(rio.Session, 'save_file', fake_save_file):
546
+ # Call the actual Session.save_file method
547
+ await Session.save_file(session, 'test content', 'test.txt', media_type='text/plain', directory=pathlib.Path('/test'))
548
+
549
+ # Should call parent method
550
+ mock_parent_save_file.assert_called_once_with(
551
+ session, 'test content', 'test.txt', media_type='text/plain', directory=pathlib.Path('/test')
552
+ )
553
+
554
+ async def test_session_save_file_running_in_window(self):
555
+ """Test save_file when running in window calls webview."""
556
+ session = Mock()
557
+ session.running_in_window = True
558
+ session.app10x = Mock()
559
+ session.app10x.webview = Mock()
560
+
561
+ # Call the actual Session.save_file method
562
+ await Session.save_file(session, 'test content', 'test.txt', media_type='text/plain', directory=pathlib.Path('/test'))
563
+
564
+ # Should call webview save_file
565
+ session.app10x.webview.save_file.assert_called_once_with(file_contents='test content', directory='/test', file_name='test.txt')
566
+
567
+ async def test_session_save_file_running_in_window_no_directory(self):
568
+ """Test save_file when running in window with no directory calls webview."""
569
+ session = Mock()
570
+ session.running_in_window = True
571
+ session.app10x = Mock()
572
+ session.app10x.webview = Mock()
573
+
574
+ # Call the actual Session.save_file method
575
+ await Session.save_file(session, 'test content', 'test.txt')
576
+
577
+ # Should call webview save_file with empty directory
578
+ session.app10x.webview.save_file.assert_called_once_with(file_contents='test content', directory='', file_name='test.txt')
579
+
580
+
581
+ class TestIntegration:
582
+ """Integration tests for the complete App10x workflow."""
583
+
584
+ @patch('ui_10x.rio.internals.app.WebViewProcess')
585
+ @patch('ui_10x.rio.internals.app.utils.ensure_valid_port')
586
+ @patch('asyncio.run')
587
+ def test_complete_app10x_workflow(self, mock_asyncio_run, mock_ensure_port, mock_webview_process):
588
+ """Test the complete App10x workflow from initialization to cleanup."""
589
+ # Setup mocks
590
+ mock_ensure_port.return_value = 8080
591
+ mock_asyncio_run.return_value = '/path/to/icon.png'
592
+ mock_webview = Mock()
593
+ mock_webview.is_alive.return_value = True
594
+ mock_webview_process.return_value = mock_webview
595
+
596
+ # Create mock Rio app
597
+ mock_app = Mock()
598
+ mock_app.name = 'Integration Test App'
599
+ mock_app._run_as_web_server = Mock()
600
+
601
+ # Create App10x instance
602
+ app10x = App10x(app=mock_app)
603
+
604
+ # Test the complete workflow
605
+ app10x._run_in_window(debug_mode=True)
606
+
607
+ # Verify WebViewProcess was created
608
+ mock_webview_process.assert_called_once()
609
+
610
+ # Verify the server was configured correctly
611
+ mock_app._run_as_web_server.assert_called_once()
612
+ call_kwargs = mock_app._run_as_web_server.call_args[1]
613
+ assert call_kwargs['debug_mode'] is True
614
+ assert call_kwargs['running_in_window'] is True
615
+
616
+ # Verify cleanup was performed
617
+ mock_webview.close.assert_called_once()
618
+ mock_webview.join.assert_called_once()
619
+
620
+ def test_session_method_attributes(self):
621
+ """Test that Session class has the correct method attributes."""
622
+ # Verify that Session has the expected method attributes
623
+ assert hasattr(Session, '_local_methods_')
624
+ assert hasattr(Session, '_remote_methods_')
625
+
626
+ # These should be copies of the parent class attributes
627
+ assert Session._local_methods_ is not rio.Session._local_methods_
628
+ assert Session._remote_methods_ is not rio.Session._remote_methods_
629
+ assert isinstance(Session._local_methods_, dict)
630
+ assert isinstance(Session._remote_methods_, dict)
@@ -0,0 +1,37 @@
1
+ import matplotlib.colors
2
+ from ui_10x.rio.style_sheet import StyleSheet
3
+
4
+ import rio
5
+
6
+
7
+ def test_ss_to_ts():
8
+ sheet = {
9
+ 'color': 'lightgreen',
10
+ 'background-color': 'white',
11
+ 'font-family': 'Helvetica',
12
+ 'font-style': 'italic',
13
+ 'font-weight': 'bold',
14
+ 'border-width': '2px',
15
+ 'border-style': '',
16
+ 'border-color': 'blue',
17
+ }
18
+ ss = StyleSheet()
19
+ rc = ss.set_values(sheet=sheet)
20
+ assert not rc
21
+ assert 'background-color' in rc.error()
22
+ assert 'border-color' in rc.error()
23
+ assert 'border-style' in rc.error()
24
+ assert 'border-style' in rc.error()
25
+ assert 'border-width' in rc.error()
26
+ ts = ss.text_style
27
+ assert ts.italic
28
+ assert ts.font_weight == 'bold'
29
+ assert ts.font._google_fonts_name == 'Helvetica'
30
+ assert matplotlib.colors.same_color(f'#{ts.fill.hex}', 'lightgreen')
31
+
32
+
33
+ def test_ts_to_ss():
34
+ ss = StyleSheet()
35
+ rc = ss.set_values(text_style=rio.TextStyle(font=rio.Font.from_google_fonts('Times New Roman'), fill=rio.Color.from_hex('#c5f7c5')))
36
+ assert rc
37
+ assert ss.sheet == {'color': '#c5f7c5', 'font-family': 'Times New Roman', 'font-style': 'normal', 'font-weight': 'normal'}