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.
- core_10x/__init__.py +42 -0
- core_10x/backbone/__init__.py +0 -0
- core_10x/backbone/backbone_store.py +59 -0
- core_10x/backbone/backbone_traitable.py +30 -0
- core_10x/backbone/backbone_user.py +66 -0
- core_10x/backbone/bound_data_domain.py +49 -0
- core_10x/backbone/namespace.py +101 -0
- core_10x/backbone/vault.py +38 -0
- core_10x/code_samples/__init__.py +0 -0
- core_10x/code_samples/_package_manifest.py +3 -0
- core_10x/code_samples/directories.py +181 -0
- core_10x/code_samples/person.py +76 -0
- core_10x/concrete_traits.py +356 -0
- core_10x/conftest.py +12 -0
- core_10x/curve.py +321 -0
- core_10x/data_domain.py +48 -0
- core_10x/data_domain_binder.py +45 -0
- core_10x/directory.py +250 -0
- core_10x/entity.py +8 -0
- core_10x/entity_filter.py +5 -0
- core_10x/environment_variables.py +147 -0
- core_10x/exec_control.py +84 -0
- core_10x/experimental/__init__.py +0 -0
- core_10x/experimental/data_protocol_ex.py +34 -0
- core_10x/global_cache.py +121 -0
- core_10x/manual_tests/__init__.py +0 -0
- core_10x/manual_tests/calendar_test.py +35 -0
- core_10x/manual_tests/ctor_update_bug.py +58 -0
- core_10x/manual_tests/debug_graph_on.py +17 -0
- core_10x/manual_tests/debug_graphoff_inside_graph_on.py +28 -0
- core_10x/manual_tests/enum_bits_test.py +17 -0
- core_10x/manual_tests/env_vars_trivial_test.py +12 -0
- core_10x/manual_tests/existing_traitable.py +33 -0
- core_10x/manual_tests/k10x_test1.py +13 -0
- core_10x/manual_tests/named_constant_test.py +121 -0
- core_10x/manual_tests/nucleus_trivial_test.py +42 -0
- core_10x/manual_tests/polars_test.py +14 -0
- core_10x/manual_tests/py_class_test.py +4 -0
- core_10x/manual_tests/rc_test.py +42 -0
- core_10x/manual_tests/rdate_test.py +12 -0
- core_10x/manual_tests/reference_serialization_bug.py +19 -0
- core_10x/manual_tests/resource_trivial_test.py +10 -0
- core_10x/manual_tests/store_uri_test.py +6 -0
- core_10x/manual_tests/trait_definition_test.py +19 -0
- core_10x/manual_tests/trait_filter_test.py +15 -0
- core_10x/manual_tests/trait_flag_modification_test.py +42 -0
- core_10x/manual_tests/trait_modification_bug.py +26 -0
- core_10x/manual_tests/traitable_as_of_test.py +82 -0
- core_10x/manual_tests/traitable_heir_test.py +39 -0
- core_10x/manual_tests/traitable_history_test.py +41 -0
- core_10x/manual_tests/traitable_serialization_test.py +54 -0
- core_10x/manual_tests/traitable_trivial_test.py +71 -0
- core_10x/manual_tests/trivial_graph_test.py +16 -0
- core_10x/manual_tests/ts_class_association_test.py +64 -0
- core_10x/manual_tests/ts_trivial_test.py +35 -0
- core_10x/named_constant.py +425 -0
- core_10x/nucleus.py +81 -0
- core_10x/package_manifest.py +85 -0
- core_10x/package_refactoring.py +153 -0
- core_10x/py_class.py +431 -0
- core_10x/rc.py +155 -0
- core_10x/rdate.py +339 -0
- core_10x/resource.py +189 -0
- core_10x/roman_number.py +67 -0
- core_10x/testlib/__init__.py +0 -0
- core_10x/testlib/test_store.py +240 -0
- core_10x/testlib/traitable_history_tests.py +787 -0
- core_10x/testlib/ts_tests.py +280 -0
- core_10x/trait.py +377 -0
- core_10x/trait_definition.py +176 -0
- core_10x/trait_filter.py +205 -0
- core_10x/trait_method_error.py +36 -0
- core_10x/traitable.py +1082 -0
- core_10x/traitable_cli.py +153 -0
- core_10x/traitable_heir.py +33 -0
- core_10x/traitable_id.py +31 -0
- core_10x/ts_store.py +172 -0
- core_10x/ts_store_type.py +26 -0
- core_10x/ts_union.py +147 -0
- core_10x/ui_hint.py +153 -0
- core_10x/unit_tests/test_concrete_traits.py +156 -0
- core_10x/unit_tests/test_converters.py +51 -0
- core_10x/unit_tests/test_curve.py +157 -0
- core_10x/unit_tests/test_directory.py +54 -0
- core_10x/unit_tests/test_documentation.py +172 -0
- core_10x/unit_tests/test_environment_variables.py +15 -0
- core_10x/unit_tests/test_filters.py +239 -0
- core_10x/unit_tests/test_graph.py +348 -0
- core_10x/unit_tests/test_named_constant.py +98 -0
- core_10x/unit_tests/test_rc.py +11 -0
- core_10x/unit_tests/test_rdate.py +484 -0
- core_10x/unit_tests/test_trait_method_error.py +80 -0
- core_10x/unit_tests/test_trait_modification.py +19 -0
- core_10x/unit_tests/test_traitable.py +959 -0
- core_10x/unit_tests/test_traitable_history.py +1 -0
- core_10x/unit_tests/test_ts_store.py +1 -0
- core_10x/unit_tests/test_ts_union.py +369 -0
- core_10x/unit_tests/test_ui_nodes.py +81 -0
- core_10x/unit_tests/test_xxcalendar.py +471 -0
- core_10x/vault/__init__.py +0 -0
- core_10x/vault/sec_keys.py +133 -0
- core_10x/vault/security_keys_old.py +168 -0
- core_10x/vault/vault.py +56 -0
- core_10x/vault/vault_traitable.py +56 -0
- core_10x/vault/vault_user.py +70 -0
- core_10x/xdate_time.py +136 -0
- core_10x/xnone.py +71 -0
- core_10x/xxcalendar.py +228 -0
- infra_10x/__init__.py +0 -0
- infra_10x/manual_tests/__init__.py +0 -0
- infra_10x/manual_tests/test_misc.py +16 -0
- infra_10x/manual_tests/test_prepare_filter_and_pipeline.py +25 -0
- infra_10x/mongodb_admin.py +111 -0
- infra_10x/mongodb_store.py +346 -0
- infra_10x/mongodb_utils.py +129 -0
- infra_10x/unit_tests/conftest.py +13 -0
- infra_10x/unit_tests/test_mongo_db.py +36 -0
- infra_10x/unit_tests/test_mongo_history.py +1 -0
- py10x_universe-0.1.3.dist-info/METADATA +406 -0
- py10x_universe-0.1.3.dist-info/RECORD +214 -0
- py10x_universe-0.1.3.dist-info/WHEEL +4 -0
- py10x_universe-0.1.3.dist-info/licenses/LICENSE +21 -0
- ui_10x/__init__.py +0 -0
- ui_10x/apps/__init__.py +0 -0
- ui_10x/apps/collection_editor_app.py +100 -0
- ui_10x/choice.py +212 -0
- ui_10x/collection_editor.py +135 -0
- ui_10x/concrete_trait_widgets.py +220 -0
- ui_10x/conftest.py +8 -0
- ui_10x/entity_stocker.py +173 -0
- ui_10x/examples/__init__.py +0 -0
- ui_10x/examples/_guess_word_data.py +14076 -0
- ui_10x/examples/collection_editor.py +17 -0
- ui_10x/examples/date_selector.py +14 -0
- ui_10x/examples/entity_stocker.py +18 -0
- ui_10x/examples/guess_word.py +392 -0
- ui_10x/examples/message_box.py +20 -0
- ui_10x/examples/multi_choice.py +17 -0
- ui_10x/examples/py_data_browser.py +66 -0
- ui_10x/examples/radiobox.py +29 -0
- ui_10x/examples/single_choice.py +31 -0
- ui_10x/examples/style_sheet.py +47 -0
- ui_10x/examples/trivial_entity_editor.py +18 -0
- ui_10x/platform.py +20 -0
- ui_10x/platform_interface.py +517 -0
- ui_10x/py_data_browser.py +249 -0
- ui_10x/qt6/__init__.py +0 -0
- ui_10x/qt6/conftest.py +8 -0
- ui_10x/qt6/manual_tests/__init__.py +0 -0
- ui_10x/qt6/manual_tests/basic_test.py +35 -0
- ui_10x/qt6/platform_implementation.py +275 -0
- ui_10x/qt6/utils.py +665 -0
- ui_10x/rio/__init__.py +0 -0
- ui_10x/rio/apps/examples/examples/__init__.py +22 -0
- ui_10x/rio/apps/examples/examples/components/__init__.py +3 -0
- ui_10x/rio/apps/examples/examples/components/collection_editor.py +15 -0
- ui_10x/rio/apps/examples/examples/pages/collection_editor.py +21 -0
- ui_10x/rio/apps/examples/examples/pages/login_page.py +88 -0
- ui_10x/rio/apps/examples/examples/pages/style_sheet.py +21 -0
- ui_10x/rio/apps/examples/rio.toml +14 -0
- ui_10x/rio/component_builder.py +497 -0
- ui_10x/rio/components/__init__.py +9 -0
- ui_10x/rio/components/group_box.py +31 -0
- ui_10x/rio/components/labeled_checkbox.py +18 -0
- ui_10x/rio/components/line_edit.py +37 -0
- ui_10x/rio/components/radio_button.py +32 -0
- ui_10x/rio/components/separator.py +24 -0
- ui_10x/rio/components/splitter.py +121 -0
- ui_10x/rio/components/tree_view.py +75 -0
- ui_10x/rio/conftest.py +35 -0
- ui_10x/rio/internals/__init__.py +0 -0
- ui_10x/rio/internals/app.py +192 -0
- ui_10x/rio/manual_tests/__init__.py +0 -0
- ui_10x/rio/manual_tests/basic_test.py +24 -0
- ui_10x/rio/manual_tests/splitter.py +27 -0
- ui_10x/rio/platform_implementation.py +91 -0
- ui_10x/rio/style_sheet.py +53 -0
- ui_10x/rio/unit_tests/test_collection_editor.py +68 -0
- ui_10x/rio/unit_tests/test_internals.py +630 -0
- ui_10x/rio/unit_tests/test_style_sheet.py +37 -0
- ui_10x/rio/widgets/__init__.py +46 -0
- ui_10x/rio/widgets/application.py +109 -0
- ui_10x/rio/widgets/button.py +48 -0
- ui_10x/rio/widgets/button_group.py +60 -0
- ui_10x/rio/widgets/calendar.py +23 -0
- ui_10x/rio/widgets/checkbox.py +24 -0
- ui_10x/rio/widgets/dialog.py +137 -0
- ui_10x/rio/widgets/group_box.py +27 -0
- ui_10x/rio/widgets/layout.py +34 -0
- ui_10x/rio/widgets/line_edit.py +37 -0
- ui_10x/rio/widgets/list.py +105 -0
- ui_10x/rio/widgets/message_box.py +70 -0
- ui_10x/rio/widgets/scroll_area.py +31 -0
- ui_10x/rio/widgets/spacer.py +6 -0
- ui_10x/rio/widgets/splitter.py +45 -0
- ui_10x/rio/widgets/text_edit.py +28 -0
- ui_10x/rio/widgets/tree.py +89 -0
- ui_10x/rio/widgets/unit_tests/test_button.py +101 -0
- ui_10x/rio/widgets/unit_tests/test_button_group.py +33 -0
- ui_10x/rio/widgets/unit_tests/test_calendar.py +114 -0
- ui_10x/rio/widgets/unit_tests/test_checkbox.py +109 -0
- ui_10x/rio/widgets/unit_tests/test_group_box.py +158 -0
- ui_10x/rio/widgets/unit_tests/test_label.py +43 -0
- ui_10x/rio/widgets/unit_tests/test_line_edit.py +140 -0
- ui_10x/rio/widgets/unit_tests/test_list.py +146 -0
- ui_10x/table_header_view.py +305 -0
- ui_10x/table_view.py +174 -0
- ui_10x/trait_editor.py +189 -0
- ui_10x/trait_widget.py +131 -0
- ui_10x/traitable_editor.py +200 -0
- ui_10x/traitable_view.py +131 -0
- ui_10x/unit_tests/conftest.py +8 -0
- ui_10x/unit_tests/test_platform.py +9 -0
- 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'}
|