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,158 @@
1
+ import asyncio
2
+
3
+ import rio.testing
4
+ from ui_10x.rio.component_builder import DynamicComponent
5
+ from ui_10x.rio.widgets import CheckBox, GroupBox, Label, PushButton
6
+
7
+
8
+ async def test_group_box() -> None:
9
+ """Test basic GroupBox widget functionality."""
10
+ widget = GroupBox(title='Test Group')
11
+
12
+ async with rio.testing.DummyClient(lambda: DynamicComponent(widget)) as test_client:
13
+ await asyncio.sleep(0.5)
14
+
15
+ # Test initial state
16
+ assert widget['title'] == 'Test Group'
17
+
18
+ # Test title change
19
+ widget.set_title('Updated Group')
20
+ await test_client.wait_for_refresh()
21
+ assert widget['title'] == 'Updated Group'
22
+
23
+
24
+ async def test_group_box_with_children() -> None:
25
+ """Test GroupBox widget with child widgets."""
26
+ label = Label('Label inside group')
27
+ checkbox = CheckBox('Checkbox inside group')
28
+ button = PushButton('Button inside group')
29
+
30
+ widget = GroupBox(None, 'Group with children', label, checkbox, button)
31
+
32
+ async with rio.testing.BrowserClient(lambda: DynamicComponent(widget)) as test_client:
33
+ await asyncio.sleep(0.5)
34
+
35
+ # Test title
36
+ assert widget['title'] == 'Group with children'
37
+
38
+ # Test children
39
+ children = widget.get_children()
40
+ assert len(children) == 3
41
+ assert label in children
42
+ assert checkbox in children
43
+ assert button in children
44
+ assert 4 == await test_client.execute_js('document.querySelectorAll(".rio-column .rio-text").length')
45
+ assert 1 == await test_client.execute_js('document.querySelectorAll(".rio-button").length')
46
+
47
+
48
+ async def test_group_box_add_remove_children() -> None:
49
+ """Test GroupBox widget adding and removing children."""
50
+ widget = GroupBox(title='Dynamic Group')
51
+ label = Label('Dynamic Label')
52
+ button = PushButton('Dynamic Button')
53
+
54
+ async with rio.testing.BrowserClient(lambda: DynamicComponent(widget)) as test_client:
55
+ await asyncio.sleep(0.5)
56
+
57
+ # Test initial empty state
58
+ assert len(widget.get_children()) == 0
59
+ assert 1 == await test_client.execute_js('document.querySelectorAll(".rio-column .rio-text").length')
60
+ assert 0 == await test_client.execute_js('document.querySelectorAll(".rio-button").length')
61
+
62
+ # Test adding children
63
+ widget.add_children(label)
64
+ await test_client.wait_for_refresh()
65
+ assert len(widget.get_children()) == 1
66
+ assert label in widget.get_children()
67
+ assert 2 == await test_client.execute_js('document.querySelectorAll(".rio-column .rio-text").length')
68
+ assert 0 == await test_client.execute_js('document.querySelectorAll(".rio-button").length')
69
+
70
+ widget.add_children(button)
71
+ await test_client.wait_for_refresh()
72
+ assert len(widget.get_children()) == 2
73
+ assert button in widget.get_children()
74
+ assert 3 == await test_client.execute_js('document.querySelectorAll(".rio-column .rio-text").length')
75
+ assert 1 == await test_client.execute_js('document.querySelectorAll(".rio-button").length')
76
+
77
+ # Test removing children
78
+ widget._kwargs['children'] = [child for child in widget._kwargs['children'] if child is not label]
79
+ widget.force_update()
80
+ await test_client.wait_for_refresh()
81
+ assert len(widget.get_children()) == 1
82
+ assert label not in widget.get_children()
83
+ assert button in widget.get_children()
84
+ assert 2 == await test_client.execute_js('document.querySelectorAll(".rio-column .rio-text").length')
85
+ assert 1 == await test_client.execute_js('document.querySelectorAll(".rio-button").length')
86
+
87
+ widget._kwargs['children'] = [child for child in widget._kwargs['children'] if child is not button]
88
+ widget.force_update()
89
+ await test_client.wait_for_refresh()
90
+ assert len(widget.get_children()) == 0
91
+ assert 1 == await test_client.execute_js('document.querySelectorAll(".rio-column .rio-text").length')
92
+ assert 0 == await test_client.execute_js('document.querySelectorAll(".rio-button").length')
93
+
94
+
95
+ # async def test_group_box_enabled_disabled() -> None:
96
+ # """Test GroupBox enabled/disabled state."""
97
+ # widget = GroupBox(title = 'Enabled Group')
98
+ # label = Label('Child Label')
99
+ # widget.add_widget(label)
100
+ #
101
+ # async with rio.testing.BrowserClient(lambda: DynamicComponent(widget)) as test_client:
102
+ # await asyncio.sleep(0.5)
103
+ #
104
+ # # Test initial enabled state
105
+ # assert widget['is_sensitive']
106
+ # assert label.is_enabled()
107
+ #
108
+ # # Test disabling group (should disable children)
109
+ # widget.set_enabled(False)
110
+ # await test_client.wait_for_refresh()
111
+ # assert not widget['is_sensitive']
112
+ # assert not label.is_enabled()
113
+ #
114
+ # # Test re-enabling
115
+ # widget.set_enabled(True)
116
+ # await test_client.wait_for_refresh()
117
+ # assert widget['is_sensitive']
118
+ # assert label.is_enabled()
119
+
120
+
121
+ async def test_group_box_client_interaction() -> None:
122
+ """Test GroupBox widget client interaction with timeout protection."""
123
+ widget = GroupBox(title='Client Test')
124
+ label = Label('Content')
125
+ widget.add_children(label)
126
+
127
+ async with rio.testing.BrowserClient(lambda: DynamicComponent(widget)) as test_client:
128
+ await asyncio.sleep(0.5)
129
+
130
+ # Test initial state
131
+ assert widget['title'] == 'Client Test'
132
+ assert len(widget.get_children()) == 1
133
+ await check_title(widget, test_client)
134
+ await check_label(label, test_client)
135
+
136
+ # Test widget property changes
137
+ widget.set_title('Updated Title')
138
+ await test_client.wait_for_refresh()
139
+ assert widget['title'] == 'Updated Title'
140
+
141
+ # Verify client state reflects widget changes
142
+ await check_title(widget, test_client)
143
+ await check_label(label, test_client)
144
+
145
+ # Test empty title
146
+ widget.set_title('')
147
+ await test_client.wait_for_refresh()
148
+ assert widget['title'] == ''
149
+ await check_title(widget, test_client)
150
+
151
+
152
+ async def check_label(label, test_client):
153
+ assert 2 == await test_client.execute_js('document.querySelectorAll(".rio-column .rio-text").length')
154
+ assert label['text'] == await test_client.execute_js('document.querySelectorAll(".rio-column .rio-text")[1].innerText')
155
+
156
+
157
+ async def check_title(widget, test_client):
158
+ assert widget['title'] == await test_client.execute_js('document.querySelectorAll(".rio-column .rio-text")[0].innerText')
@@ -0,0 +1,43 @@
1
+ import asyncio
2
+
3
+ import rio.testing.browser_client
4
+ from ui_10x.rio.component_builder import DynamicComponent
5
+ from ui_10x.rio.widgets import Label
6
+
7
+
8
+ async def check_text(widget, test_client, text):
9
+ assert widget['text'] == text
10
+ client_text = await test_client.execute_js('document.querySelector(".rio-text")?.children[0]?.innerText || ""')
11
+ assert client_text == text
12
+
13
+
14
+ async def test_label() -> None:
15
+ """Test basic Label widget functionality with client interaction verification."""
16
+ widget = Label('Hello World')
17
+
18
+ async with rio.testing.BrowserClient(lambda: DynamicComponent(widget)) as test_client:
19
+ await asyncio.sleep(0.5)
20
+
21
+ # Test initial text - verify widget state
22
+ assert widget['text'] == 'Hello World'
23
+
24
+ # Test text change - verify widget update
25
+ widget.set_text('Updated Text')
26
+ await test_client.wait_for_refresh()
27
+ await check_text(widget, test_client, 'Updated Text')
28
+
29
+
30
+ async def test_label_empty() -> None:
31
+ """Test Label widget with empty text and client validation."""
32
+ widget = Label()
33
+
34
+ async with rio.testing.BrowserClient(lambda: DynamicComponent(widget)) as test_client:
35
+ await asyncio.sleep(0.5)
36
+
37
+ # Test empty text
38
+ await check_text(widget, test_client, '')
39
+
40
+ # Test setting text
41
+ widget.set_text('New Text')
42
+ await test_client.wait_for_refresh()
43
+ await check_text(widget, test_client, 'New Text')
@@ -0,0 +1,140 @@
1
+ import asyncio
2
+
3
+ import pytest
4
+ import rio.testing
5
+ from ui_10x import platform_interface as i
6
+ from ui_10x.rio.component_builder import DynamicComponent
7
+ from ui_10x.rio.widgets import LineEdit
8
+
9
+
10
+ @pytest.mark.async_timeout(10)
11
+ async def test_line_edit_comprehensive() -> None:
12
+ """Test LineEdit with comprehensive client-widget interaction verification."""
13
+ widget = LineEdit('Initial Text')
14
+ find_input = 'document.querySelector(".rio-input-box").querySelector("input")'
15
+ find_tool_tip = 'document.querySelector(".rio-tooltip-popup").querySelector(".rio-text").children[0].innerText'
16
+
17
+ edited_calls = []
18
+ finished_calls = []
19
+
20
+ def edited_handler(text):
21
+ edited_calls.append(text)
22
+
23
+ def finished_handler():
24
+ finished_calls.append(True)
25
+
26
+ widget.text_edited_connect(edited_handler)
27
+ widget.editing_finished_connect(finished_handler)
28
+
29
+ async with rio.testing.BrowserClient(lambda: DynamicComponent(widget)) as test_client:
30
+ await asyncio.sleep(0.5)
31
+
32
+ # 1) Verify client shows widget value
33
+ assert widget.text() == 'Initial Text'
34
+ assert widget.text() == await test_client.execute_js(find_input + '.value')
35
+
36
+ # 2) Modify client value (user typing)
37
+ await test_client.execute_js(find_input + '.focus();')
38
+ await test_client.execute_js(find_input + '.value = "User Typed Text";')
39
+ await test_client.execute_js(find_input + '.dispatchEvent(new Event("input"));')
40
+ await asyncio.sleep(1) # wait for change delay
41
+
42
+ # 3) Verify widget reflects client value
43
+ assert widget.text() == 'User Typed Text'
44
+ assert widget.text() == await test_client.execute_js(find_input + '.value')
45
+ assert len(edited_calls) == 1
46
+ assert widget.text() == edited_calls[0]
47
+
48
+ # Test editing finished
49
+ await test_client.execute_js(find_input + '.blur();')
50
+ assert len(finished_calls) == 1
51
+ assert widget.text() == await test_client.execute_js(find_input + '.value')
52
+
53
+ # Test widget changes propagate to client with timeout protection
54
+ widget.set_text('Widget Updated')
55
+ await test_client.wait_for_refresh()
56
+ client_value = await test_client.execute_js(find_input + '.value')
57
+ assert client_value == 'Widget Updated'
58
+ assert widget.text() == 'Widget Updated'
59
+
60
+ # Test tooltip functionality
61
+ widget.set_tool_tip('Helpful tip')
62
+ await test_client.wait_for_refresh()
63
+ await test_client._page.mouse.move(1, 1)
64
+ tooltip_text = await test_client.execute_js(find_tool_tip)
65
+ assert tooltip_text == 'Helpful tip'
66
+
67
+ # Test password mode with timeout protection
68
+ assert 'password' != await test_client.execute_js(find_input + '.type')
69
+ widget.set_password_mode()
70
+ await test_client.wait_for_refresh()
71
+ assert 'password' == await test_client.execute_js(find_input + '.type')
72
+
73
+
74
+ async def test_line_edit_disabled_interaction() -> None:
75
+ """Test LineEdit disabled state blocks user interaction."""
76
+
77
+ right_button_presses = []
78
+ left_button_presses = []
79
+
80
+ class MyLineEdit(LineEdit):
81
+ def mouse_press_event(self, event: i.MouseEvent):
82
+ if event.is_right_button():
83
+ right_button_presses.append(event)
84
+ else:
85
+ left_button_presses.append(event)
86
+
87
+ widget = MyLineEdit('Initial Text')
88
+ find_input = 'document.querySelector(".rio-input-box input")'
89
+
90
+ edited_calls = []
91
+
92
+ def edited_handler(text):
93
+ edited_calls.append(text)
94
+
95
+ widget.text_edited_connect(edited_handler)
96
+
97
+ async with rio.testing.BrowserClient(lambda: DynamicComponent(widget)) as test_client:
98
+ await asyncio.sleep(0.5)
99
+
100
+ # Test initial enabled state - typing should work
101
+ async def test_interaction(old_content, new_content, enabled=True):
102
+ initial_left = len(left_button_presses)
103
+ initial_right = len(right_button_presses)
104
+ initial_edited = len(edited_calls)
105
+ await test_client.click(10, 20, button='right') # handler
106
+ await test_client._page.click('.rio-input-box input', click_count=3, force=True) # select all
107
+ await test_client._page.keyboard.press(new_content)
108
+ await asyncio.sleep(1)
109
+
110
+ right, left, edited, content = (1, 3, 1, new_content) if enabled else (1, 3, 0, old_content)
111
+
112
+ assert len(right_button_presses) == right + initial_right
113
+ assert len(left_button_presses) == left + initial_left
114
+ assert len(edited_calls) == edited + initial_edited
115
+ assert edited_calls[-1] == content
116
+ assert widget.text() == content
117
+
118
+ await test_interaction(widget.text(), 'A')
119
+
120
+ # Disable widget
121
+ widget.set_enabled(False)
122
+ await test_client.wait_for_refresh()
123
+
124
+ # Verify client shows disabled state
125
+ client_disabled = await test_client.execute_js(find_input + '.disabled')
126
+ assert client_disabled is True
127
+
128
+ # Test that typing is blocked when disabled
129
+ await test_interaction(widget.text(), 'B', enabled=False)
130
+
131
+ # Re-enable widget
132
+ widget.set_enabled(True)
133
+ await test_client.wait_for_refresh()
134
+
135
+ # Verify client shows enabled state
136
+ client_disabled = await test_client.execute_js(find_input + '.disabled')
137
+ assert client_disabled is False
138
+
139
+ # Test that typing works again
140
+ await test_interaction(widget.text(), 'C')
@@ -0,0 +1,146 @@
1
+ import asyncio
2
+
3
+ import rio.testing.browser_client
4
+ from ui_10x.rio.component_builder import DynamicComponent
5
+ from ui_10x.rio.widgets import ListWidget
6
+
7
+
8
+ async def test_list_comprehensive() -> None:
9
+ """Test ListWidget with comprehensive client-widget interaction verification."""
10
+ widget = ListWidget()
11
+ widget.add_items(['Item 1', 'Item 2', 'Item 3'])
12
+
13
+ find_list_items = 'document.querySelectorAll(".rio-selectable-item")'
14
+ find_selected_text = 'document.querySelector(".selected").querySelector(".rio-text").children[0].innerText'
15
+
16
+ clicked_calls = []
17
+
18
+ def clicked_handler(item):
19
+ clicked_calls.append(item)
20
+
21
+ widget.clicked_connect(clicked_handler)
22
+
23
+ async with rio.testing.BrowserClient(lambda: DynamicComponent(widget)) as test_client:
24
+ await asyncio.sleep(0.5)
25
+
26
+ # 1) Verify client shows widget value (list items)
27
+ assert widget.child_count() == 3
28
+ client_items = await test_client.execute_js(f'{find_list_items}.length')
29
+ assert client_items == 3
30
+
31
+ # 2) Modify client value (user clicking first item)
32
+ await test_client.click(10, 1)
33
+ await asyncio.sleep(0.5)
34
+
35
+ # 3) Verify widget reflects client value
36
+ assert len(clicked_calls) == 1
37
+ assert clicked_calls[0]['text'] == 'Item 1'
38
+ assert widget['selected_items'] == [clicked_calls[0]['key']]
39
+
40
+ # Verify client shows selection
41
+ selected_text = await test_client.execute_js(find_selected_text)
42
+ assert selected_text == 'Item 1'
43
+
44
+ # Test clicking second item
45
+ await test_client.click(10, test_client.window_height_in_pixels - 1)
46
+ await asyncio.sleep(0.5)
47
+ assert len(clicked_calls) == 2
48
+ assert clicked_calls[1]['text'] == 'Item 3' # Last item
49
+ assert widget['selected_items'] == [clicked_calls[1]['key']]
50
+
51
+ # Test widget changes propagate to client (clearing list)
52
+ widget.clear()
53
+ assert not widget.get_children()
54
+ await test_client.wait_for_refresh()
55
+ assert not widget.subcomponent.selected_items
56
+ client_items = await test_client.execute_js(f'{find_list_items}.length')
57
+ assert client_items == 0
58
+
59
+ # Test adding item
60
+ widget.add_item('New Item')
61
+ await test_client.wait_for_refresh()
62
+ assert widget.child_count() == 1
63
+ client_items = await test_client.execute_js(f'{find_list_items}.length')
64
+ assert client_items == 1
65
+
66
+ # Test clicking new item
67
+ await test_client.click(10, 1)
68
+ await asyncio.sleep(0.5)
69
+ assert len(clicked_calls) == 3
70
+ assert clicked_calls[2]['text'] == 'New Item'
71
+
72
+
73
+ async def test_list_item_management() -> None:
74
+ """Test ListWidget item management operations."""
75
+ widget = ListWidget()
76
+ widget.add_items(['A', 'B', 'C'])
77
+
78
+ find_list_items = 'document.querySelectorAll(".rio-selectable-item")'
79
+
80
+ async with rio.testing.BrowserClient(lambda: DynamicComponent(widget)) as test_client:
81
+ await asyncio.sleep(0.5)
82
+
83
+ # Test initial state
84
+ assert widget.child_count() == 3
85
+ client_items = await test_client.execute_js(f'{find_list_items}.length')
86
+ assert client_items == 3
87
+
88
+ # Test removing item
89
+ removed_item = widget.take_item(1) # Remove 'B'
90
+ await test_client.wait_for_refresh()
91
+ assert widget.child_count() == 2
92
+ client_items = await test_client.execute_js(f'{find_list_items}.length')
93
+ assert client_items == 2
94
+ assert removed_item['text'] == 'B'
95
+
96
+ # Test adding another item with timeout protection
97
+ widget.add_item('Inserted')
98
+ await test_client.wait_for_refresh()
99
+ assert widget.child_count() == 3
100
+ client_items = await test_client.execute_js(f'{find_list_items}.length')
101
+ assert client_items == 3
102
+
103
+
104
+ async def test_list_disabled_interaction() -> None:
105
+ """Test ListWidget disabled state blocks user interaction."""
106
+ widget = ListWidget()
107
+ widget.add_items(['Item 1', 'Item 2', 'Item 3'])
108
+
109
+ clicked_calls = []
110
+
111
+ def clicked_handler(item):
112
+ clicked_calls.append(item)
113
+
114
+ widget.clicked_connect(clicked_handler)
115
+
116
+ async with rio.testing.BrowserClient(lambda: DynamicComponent(widget)) as test_client:
117
+ await asyncio.sleep(0.5)
118
+
119
+ # Test initial enabled state - clicks should work
120
+ await test_client.click(10, 1)
121
+ await asyncio.sleep(0.5)
122
+ assert len(clicked_calls) == 1
123
+ assert clicked_calls[0]['text'] == 'Item 1'
124
+ assert widget['selected_items'] == [id(widget.get_children()[0])]
125
+
126
+ # Disable widget
127
+ widget.set_enabled(False)
128
+ await test_client.wait_for_refresh()
129
+
130
+ # Test that clicks are blocked when disabled
131
+ initial_calls = len(clicked_calls)
132
+ await test_client.click(10, 1)
133
+ await asyncio.sleep(0.5)
134
+ assert len(clicked_calls) == initial_calls # No additional calls
135
+ assert widget['selected_items'] == [id(widget.get_children()[0])]
136
+
137
+ # Re-enable widget
138
+ widget.set_enabled(True)
139
+ await test_client.wait_for_refresh()
140
+
141
+ # Test that clicks work again
142
+ await test_client.click(10, 1)
143
+ await asyncio.sleep(0.5)
144
+ assert len(clicked_calls) == initial_calls + 1
145
+ assert clicked_calls[-1]['text'] == 'Item 1'
146
+ assert widget['selected_items'] == []