quantum-framework 0.9.0__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.
- quantum/__init__.py +1 -0
- quantum/cli/__init__.py +2 -0
- quantum/cli/commands/__init__.py +15 -0
- quantum/cli/commands/build.py +417 -0
- quantum/cli/commands/dev.py +185 -0
- quantum/cli/commands/docs.py +329 -0
- quantum/cli/commands/lint.py +523 -0
- quantum/cli/commands/migrate.py +527 -0
- quantum/cli/commands/new.py +622 -0
- quantum/cli/commands/serve.py +190 -0
- quantum/cli/commands/test.py +193 -0
- quantum/cli/deploy.py +810 -0
- quantum/cli/hot_reload.py +951 -0
- quantum/cli/jobs.py +356 -0
- quantum/cli/mq.py +582 -0
- quantum/cli/pkg.py +390 -0
- quantum/cli/runner.py +547 -0
- quantum/cli/server_process.py +159 -0
- quantum/cli/utils.py +334 -0
- quantum/compiler/__init__.py +30 -0
- quantum/compiler/base_generator.py +367 -0
- quantum/compiler/cli.py +295 -0
- quantum/compiler/expression_transformer.py +444 -0
- quantum/compiler/javascript/__init__.py +10 -0
- quantum/compiler/javascript/generator.py +659 -0
- quantum/compiler/optimizer.py +270 -0
- quantum/compiler/python/__init__.py +10 -0
- quantum/compiler/python/generator.py +883 -0
- quantum/compiler/python/runtime.py +863 -0
- quantum/compiler/transpiler.py +330 -0
- quantum/core/__init__.py +3 -0
- quantum/core/ast_nodes.py +2611 -0
- quantum/core/expression_diagnostics.py +87 -0
- quantum/core/expression_stdlib.py +148 -0
- quantum/core/expressions.py +591 -0
- quantum/core/features/agents/src/__init__.py +30 -0
- quantum/core/features/agents/src/ast_node.py +540 -0
- quantum/core/features/conditionals/src/__init__.py +8 -0
- quantum/core/features/conditionals/src/ast_node.py +68 -0
- quantum/core/features/data_fetching/src/__init__.py +21 -0
- quantum/core/features/data_fetching/src/ast_node.py +312 -0
- quantum/core/features/data_fetching/src/desktop_adapter.py +351 -0
- quantum/core/features/data_fetching/src/html_adapter.py +474 -0
- quantum/core/features/data_fetching/src/parser.py +225 -0
- quantum/core/features/data_import/src/__init__.py +0 -0
- quantum/core/features/data_import/src/ast_node.py +291 -0
- quantum/core/features/data_import/src/runtime.py +538 -0
- quantum/core/features/dump/src/__init__.py +12 -0
- quantum/core/features/dump/src/ast_node.py +106 -0
- quantum/core/features/dump/src/parser.py +61 -0
- quantum/core/features/dump/src/runtime.py +246 -0
- quantum/core/features/functions/src/__init__.py +8 -0
- quantum/core/features/functions/src/ast_node.py +149 -0
- quantum/core/features/game_engine_2d/src/__init__.py +19 -0
- quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
- quantum/core/features/game_engine_2d/src/parser.py +983 -0
- quantum/core/features/invocation/src/__init__.py +0 -0
- quantum/core/features/invocation/src/ast_node.py +146 -0
- quantum/core/features/invocation/src/runtime.py +327 -0
- quantum/core/features/knowledge_base/src/__init__.py +6 -0
- quantum/core/features/knowledge_base/src/ast_node.py +113 -0
- quantum/core/features/knowledge_base/src/parser.py +82 -0
- quantum/core/features/logging/src/__init__.py +12 -0
- quantum/core/features/logging/src/ast_node.py +111 -0
- quantum/core/features/logging/src/parser.py +50 -0
- quantum/core/features/logging/src/runtime.py +190 -0
- quantum/core/features/loops/src/__init__.py +8 -0
- quantum/core/features/loops/src/ast_node.py +60 -0
- quantum/core/features/query/src/__init__.py +0 -0
- quantum/core/features/query/src/database_service.py +322 -0
- quantum/core/features/query/src/query_validators.py +20 -0
- quantum/core/features/state_management/src/__init__.py +11 -0
- quantum/core/features/state_management/src/ast_node.py +228 -0
- quantum/core/features/terminal_engine/src/__init__.py +21 -0
- quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
- quantum/core/features/terminal_engine/src/parser.py +361 -0
- quantum/core/features/testing_engine/src/__init__.py +41 -0
- quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
- quantum/core/features/testing_engine/src/parser.py +604 -0
- quantum/core/features/theming/src/__init__.py +48 -0
- quantum/core/features/theming/src/ast_node.py +137 -0
- quantum/core/features/theming/src/presets.py +405 -0
- quantum/core/features/ui_engine/src/__init__.py +20 -0
- quantum/core/features/ui_engine/src/ast_nodes.py +1854 -0
- quantum/core/features/ui_engine/src/parser.py +1106 -0
- quantum/core/features/websocket/src/__init__.py +24 -0
- quantum/core/features/websocket/src/ast_node.py +247 -0
- quantum/core/html_compat.py +299 -0
- quantum/core/parser.py +1235 -0
- quantum/core/parser_registry.py +213 -0
- quantum/core/parsers/__init__.py +76 -0
- quantum/core/parsers/ai/__init__.py +12 -0
- quantum/core/parsers/ai/agent_parser.py +106 -0
- quantum/core/parsers/ai/knowledge_parser.py +86 -0
- quantum/core/parsers/ai/llm_parser.py +78 -0
- quantum/core/parsers/ai/team_parser.py +88 -0
- quantum/core/parsers/base.py +322 -0
- quantum/core/parsers/composition/__init__.py +10 -0
- quantum/core/parsers/composition/import_parser.py +50 -0
- quantum/core/parsers/composition/slot_parser.py +48 -0
- quantum/core/parsers/control_flow/__init__.py +11 -0
- quantum/core/parsers/control_flow/if_parser.py +68 -0
- quantum/core/parsers/control_flow/loop_parser.py +105 -0
- quantum/core/parsers/control_flow/set_parser.py +89 -0
- quantum/core/parsers/data/__init__.py +12 -0
- quantum/core/parsers/data/data_parser.py +217 -0
- quantum/core/parsers/data/invoke_parser.py +116 -0
- quantum/core/parsers/data/query_parser.py +188 -0
- quantum/core/parsers/data/transaction_parser.py +77 -0
- quantum/core/parsers/events/__init__.py +9 -0
- quantum/core/parsers/events/dispatch_event_parser.py +44 -0
- quantum/core/parsers/forms/__init__.py +11 -0
- quantum/core/parsers/forms/action_parser.py +54 -0
- quantum/core/parsers/forms/flash_parser.py +34 -0
- quantum/core/parsers/forms/redirect_parser.py +33 -0
- quantum/core/parsers/functions/__init__.py +11 -0
- quantum/core/parsers/functions/function_parser.py +137 -0
- quantum/core/parsers/functions/param_parser.py +66 -0
- quantum/core/parsers/functions/return_parser.py +31 -0
- quantum/core/parsers/html/__init__.py +10 -0
- quantum/core/parsers/html/component_call_parser.py +130 -0
- quantum/core/parsers/html/html_parser.py +115 -0
- quantum/core/parsers/jobs/__init__.py +11 -0
- quantum/core/parsers/jobs/job_parser.py +71 -0
- quantum/core/parsers/jobs/schedule_parser.py +62 -0
- quantum/core/parsers/jobs/thread_parser.py +57 -0
- quantum/core/parsers/messaging/__init__.py +17 -0
- quantum/core/parsers/messaging/message_ack_parser.py +30 -0
- quantum/core/parsers/messaging/message_nack_parser.py +30 -0
- quantum/core/parsers/messaging/message_parser.py +114 -0
- quantum/core/parsers/messaging/queue_parser.py +61 -0
- quantum/core/parsers/messaging/websocket_parser.py +121 -0
- quantum/core/parsers/persistence/__init__.py +9 -0
- quantum/core/parsers/persistence/persist_parser.py +64 -0
- quantum/core/parsers/routing/__init__.py +9 -0
- quantum/core/parsers/routing/route_parser.py +41 -0
- quantum/core/parsers/scripting/__init__.py +12 -0
- quantum/core/parsers/scripting/pyclass_parser.py +62 -0
- quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
- quantum/core/parsers/scripting/pyimport_parser.py +52 -0
- quantum/core/parsers/scripting/python_parser.py +49 -0
- quantum/core/parsers/services/__init__.py +12 -0
- quantum/core/parsers/services/dump_parser.py +52 -0
- quantum/core/parsers/services/file_parser.py +48 -0
- quantum/core/parsers/services/log_parser.py +46 -0
- quantum/core/parsers/services/mail_parser.py +65 -0
- quantum/core/tiers.py +82 -0
- quantum/packages/__init__.py +28 -0
- quantum/packages/manager.py +413 -0
- quantum/packages/manifest.py +351 -0
- quantum/packages/registry.py +399 -0
- quantum/packages/resolver.py +336 -0
- quantum/plugins/__init__.py +33 -0
- quantum/plugins/hooks.py +329 -0
- quantum/plugins/loader.py +479 -0
- quantum/plugins/manifest.py +336 -0
- quantum/plugins/registry.py +371 -0
- quantum/runtime/__init__.py +28 -0
- quantum/runtime/action_handler.py +443 -0
- quantum/runtime/adapters/__init__.py +88 -0
- quantum/runtime/adapters/memory_adapter.py +690 -0
- quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
- quantum/runtime/adapters/redis_adapter.py +582 -0
- quantum/runtime/adapters/sqlite_adapter.py +414 -0
- quantum/runtime/agent_service.py +1133 -0
- quantum/runtime/api_server.py +86 -0
- quantum/runtime/ast_cache.py +506 -0
- quantum/runtime/auth_service.py +267 -0
- quantum/runtime/component.py +990 -0
- quantum/runtime/component_composer.py +319 -0
- quantum/runtime/component_resolver.py +174 -0
- quantum/runtime/database_service.py +598 -0
- quantum/runtime/email_service.py +162 -0
- quantum/runtime/error_handler.py +295 -0
- quantum/runtime/execution_context.py +286 -0
- quantum/runtime/executor_registry.py +171 -0
- quantum/runtime/executors/__init__.py +71 -0
- quantum/runtime/executors/ai/__init__.py +12 -0
- quantum/runtime/executors/ai/agent_executor.py +217 -0
- quantum/runtime/executors/ai/knowledge_executor.py +114 -0
- quantum/runtime/executors/ai/llm_executor.py +153 -0
- quantum/runtime/executors/ai/team_executor.py +171 -0
- quantum/runtime/executors/base.py +262 -0
- quantum/runtime/executors/control_flow/__init__.py +11 -0
- quantum/runtime/executors/control_flow/if_executor.py +93 -0
- quantum/runtime/executors/control_flow/loop_executor.py +307 -0
- quantum/runtime/executors/control_flow/set_executor.py +412 -0
- quantum/runtime/executors/data/__init__.py +12 -0
- quantum/runtime/executors/data/data_executor.py +145 -0
- quantum/runtime/executors/data/invoke_executor.py +176 -0
- quantum/runtime/executors/data/query_executor.py +256 -0
- quantum/runtime/executors/data/transaction_executor.py +91 -0
- quantum/runtime/executors/jobs/__init__.py +11 -0
- quantum/runtime/executors/jobs/job_executor.py +190 -0
- quantum/runtime/executors/jobs/schedule_executor.py +132 -0
- quantum/runtime/executors/jobs/thread_executor.py +127 -0
- quantum/runtime/executors/messaging/__init__.py +17 -0
- quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
- quantum/runtime/executors/messaging/message_executor.py +174 -0
- quantum/runtime/executors/messaging/queue_executor.py +103 -0
- quantum/runtime/executors/messaging/websocket_executor.py +197 -0
- quantum/runtime/executors/scripting/__init__.py +11 -0
- quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
- quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
- quantum/runtime/executors/scripting/python_executor.py +249 -0
- quantum/runtime/executors/services/__init__.py +12 -0
- quantum/runtime/executors/services/dump_executor.py +72 -0
- quantum/runtime/executors/services/file_executor.py +89 -0
- quantum/runtime/executors/services/log_executor.py +77 -0
- quantum/runtime/executors/services/mail_executor.py +81 -0
- quantum/runtime/expression_cache.py +498 -0
- quantum/runtime/file_upload_service.py +326 -0
- quantum/runtime/function_registry.py +118 -0
- quantum/runtime/game_builder.py +166 -0
- quantum/runtime/game_code_generator.py +2371 -0
- quantum/runtime/game_templates.py +2006 -0
- quantum/runtime/godot_code_generator.py +4681 -0
- quantum/runtime/godot_templates.py +1449 -0
- quantum/runtime/job_executor.py +1599 -0
- quantum/runtime/knowledge_service.py +500 -0
- quantum/runtime/llm_cache.py +100 -0
- quantum/runtime/llm_providers.py +704 -0
- quantum/runtime/llm_service.py +287 -0
- quantum/runtime/logging_setup.py +140 -0
- quantum/runtime/message_broker.py +364 -0
- quantum/runtime/message_queue_service.py +571 -0
- quantum/runtime/param_validation.py +184 -0
- quantum/runtime/pypy_compat.py +315 -0
- quantum/runtime/python_bridge.py +698 -0
- quantum/runtime/query_validators.py +304 -0
- quantum/runtime/renderer.py +733 -0
- quantum/runtime/service_container.py +444 -0
- quantum/runtime/terminal_builder.py +76 -0
- quantum/runtime/terminal_code_generator.py +607 -0
- quantum/runtime/terminal_templates.py +243 -0
- quantum/runtime/testing_builder.py +77 -0
- quantum/runtime/testing_code_generator.py +833 -0
- quantum/runtime/testing_templates.py +85 -0
- quantum/runtime/ui_builder.py +188 -0
- quantum/runtime/ui_desktop_adapter.py +1730 -0
- quantum/runtime/ui_desktop_templates.py +307 -0
- quantum/runtime/ui_html_adapter.py +2691 -0
- quantum/runtime/ui_html_templates.py +2297 -0
- quantum/runtime/ui_mobile_adapter.py +1832 -0
- quantum/runtime/ui_mobile_templates.py +1003 -0
- quantum/runtime/ui_textual_adapter.py +1866 -0
- quantum/runtime/ui_textual_templates.py +45 -0
- quantum/runtime/ui_tokens.py +465 -0
- quantum/runtime/ui_validator.py +365 -0
- quantum/runtime/validators.py +256 -0
- quantum/runtime/web_server.py +1766 -0
- quantum/runtime/websocket_adapter.py +501 -0
- quantum/runtime/websocket_service.py +585 -0
- quantum/runtime/websocket_transport.py +289 -0
- quantum/runtime/wsgi.py +101 -0
- quantum/utils/__init__.py +1 -0
- quantum_framework-0.9.0.dist-info/METADATA +244 -0
- quantum_framework-0.9.0.dist-info/RECORD +262 -0
- quantum_framework-0.9.0.dist-info/WHEEL +5 -0
- quantum_framework-0.9.0.dist-info/entry_points.txt +2 -0
- quantum_framework-0.9.0.dist-info/licenses/LICENSE +21 -0
- quantum_framework-0.9.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Testing Engine Parser - Parse qtest: namespace elements into Testing AST nodes.
|
|
3
|
+
|
|
4
|
+
This module provides parse functions for all testing-specific tags.
|
|
5
|
+
It is called from the main QuantumParser when a qtest: prefixed tag is encountered.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from xml.etree import ElementTree as ET
|
|
9
|
+
from typing import Optional, List
|
|
10
|
+
|
|
11
|
+
from .ast_nodes import (
|
|
12
|
+
# Core testing nodes
|
|
13
|
+
QTestSuiteNode, QTestCaseNode, ExpectNode, MockNode_Testing,
|
|
14
|
+
FixtureNode_Testing, SetupNode_Testing, TeardownNode_Testing,
|
|
15
|
+
BeforeEachNode, AfterEachNode, GenerateNode,
|
|
16
|
+
ScenarioNode, GivenNode, WhenNode, ThenNode,
|
|
17
|
+
# Browser testing nodes
|
|
18
|
+
BrowserConfigNode, NavigateNode, ClickNode, DblClickNode,
|
|
19
|
+
RightClickNode, HoverNode, FillNode, TypeNode_Testing,
|
|
20
|
+
SelectNode_Testing, CheckNode, UncheckNode, KeyboardNode,
|
|
21
|
+
DragNode, ScrollNode_Testing, UploadNode, DownloadNode,
|
|
22
|
+
WaitForNode, ScreenshotNode, InterceptNode, InterceptRespondNode,
|
|
23
|
+
FrameNode_Testing, PopupNode, AuthNode, SaveStateNode, PauseNode,
|
|
24
|
+
# Advanced testing nodes
|
|
25
|
+
SnapshotNode, FuzzNode, PropertyNode, FuzzFieldNode,
|
|
26
|
+
PerfNode, PerfMetricNode, A11yNode, ChaosNode, ChaosFailNode,
|
|
27
|
+
GeolocationNode,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TestingParseError(Exception):
|
|
32
|
+
"""Testing-specific parse error."""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TestingParser:
|
|
37
|
+
"""Parser for qtest: namespace testing elements."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, parent_parser):
|
|
40
|
+
"""
|
|
41
|
+
Args:
|
|
42
|
+
parent_parser: The main QuantumParser instance, used to parse
|
|
43
|
+
q: namespace children (q:set, q:function, etc.)
|
|
44
|
+
"""
|
|
45
|
+
self.parent = parent_parser
|
|
46
|
+
|
|
47
|
+
# ------------------------------------------------------------------
|
|
48
|
+
# Dispatch
|
|
49
|
+
# ------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
TESTING_TAG_MAP = {
|
|
52
|
+
# Core testing
|
|
53
|
+
'suite': '_parse_testing_suite',
|
|
54
|
+
'case': '_parse_testing_case',
|
|
55
|
+
'expect': '_parse_testing_expect',
|
|
56
|
+
'mock': '_parse_testing_mock',
|
|
57
|
+
'fixture': '_parse_testing_fixture',
|
|
58
|
+
'setup': '_parse_testing_setup',
|
|
59
|
+
'teardown': '_parse_testing_teardown',
|
|
60
|
+
'before-each': '_parse_testing_before_each',
|
|
61
|
+
'after-each': '_parse_testing_after_each',
|
|
62
|
+
'generate': '_parse_testing_generate',
|
|
63
|
+
'scenario': '_parse_testing_scenario',
|
|
64
|
+
'given': '_parse_testing_given',
|
|
65
|
+
'when': '_parse_testing_when',
|
|
66
|
+
'then': '_parse_testing_then',
|
|
67
|
+
# Browser testing
|
|
68
|
+
'browser-config': '_parse_testing_browser_config',
|
|
69
|
+
'navigate': '_parse_testing_navigate',
|
|
70
|
+
'click': '_parse_testing_click',
|
|
71
|
+
'dblclick': '_parse_testing_dblclick',
|
|
72
|
+
'right-click': '_parse_testing_right_click',
|
|
73
|
+
'hover': '_parse_testing_hover',
|
|
74
|
+
'fill': '_parse_testing_fill',
|
|
75
|
+
'type': '_parse_testing_type',
|
|
76
|
+
'select': '_parse_testing_select',
|
|
77
|
+
'check': '_parse_testing_check',
|
|
78
|
+
'uncheck': '_parse_testing_uncheck',
|
|
79
|
+
'keyboard': '_parse_testing_keyboard',
|
|
80
|
+
'drag': '_parse_testing_drag',
|
|
81
|
+
'scroll': '_parse_testing_scroll',
|
|
82
|
+
'upload': '_parse_testing_upload',
|
|
83
|
+
'download': '_parse_testing_download',
|
|
84
|
+
'wait-for': '_parse_testing_wait_for',
|
|
85
|
+
'screenshot': '_parse_testing_screenshot',
|
|
86
|
+
'intercept': '_parse_testing_intercept',
|
|
87
|
+
'respond': '_parse_testing_respond',
|
|
88
|
+
'frame': '_parse_testing_frame',
|
|
89
|
+
'popup': '_parse_testing_popup',
|
|
90
|
+
'auth': '_parse_testing_auth',
|
|
91
|
+
'save-state': '_parse_testing_save_state',
|
|
92
|
+
'pause': '_parse_testing_pause',
|
|
93
|
+
# Advanced testing
|
|
94
|
+
'snapshot': '_parse_testing_snapshot',
|
|
95
|
+
'fuzz': '_parse_testing_fuzz',
|
|
96
|
+
'property': '_parse_testing_property',
|
|
97
|
+
'field': '_parse_testing_field',
|
|
98
|
+
'perf': '_parse_testing_perf',
|
|
99
|
+
'metric': '_parse_testing_metric',
|
|
100
|
+
'a11y': '_parse_testing_a11y',
|
|
101
|
+
'chaos': '_parse_testing_chaos',
|
|
102
|
+
'fail': '_parse_testing_fail',
|
|
103
|
+
'geolocation': '_parse_testing_geolocation',
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
def parse_testing_element(self, local_name: str, element: ET.Element):
|
|
107
|
+
"""Dispatch a qtest: element to the correct parse method."""
|
|
108
|
+
method_name = self.TESTING_TAG_MAP.get(local_name)
|
|
109
|
+
if method_name is None:
|
|
110
|
+
raise TestingParseError(f"Unknown testing tag: qtest:{local_name}")
|
|
111
|
+
method = getattr(self, method_name)
|
|
112
|
+
return method(element)
|
|
113
|
+
|
|
114
|
+
# ------------------------------------------------------------------
|
|
115
|
+
# Helpers
|
|
116
|
+
# ------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
def _get_local_name(self, element: ET.Element) -> str:
|
|
119
|
+
tag = element.tag
|
|
120
|
+
if '}' in tag:
|
|
121
|
+
return tag.split('}')[-1]
|
|
122
|
+
if ':' in tag:
|
|
123
|
+
return tag.split(':')[-1]
|
|
124
|
+
return tag
|
|
125
|
+
|
|
126
|
+
def _get_namespace(self, element: ET.Element) -> Optional[str]:
|
|
127
|
+
tag = element.tag
|
|
128
|
+
if '{https://quantum.lang/testing}' in tag:
|
|
129
|
+
return 'testing'
|
|
130
|
+
if '{https://quantum.lang/ns}' in tag:
|
|
131
|
+
return 'quantum'
|
|
132
|
+
if tag.startswith('qtest:'):
|
|
133
|
+
return 'testing'
|
|
134
|
+
if tag.startswith('q:'):
|
|
135
|
+
return 'quantum'
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
def _parse_float(self, value: Optional[str], default: float = 0) -> float:
|
|
139
|
+
if value is None:
|
|
140
|
+
return default
|
|
141
|
+
try:
|
|
142
|
+
return float(value)
|
|
143
|
+
except ValueError:
|
|
144
|
+
return default
|
|
145
|
+
|
|
146
|
+
def _parse_int(self, value: Optional[str], default: int = 0) -> int:
|
|
147
|
+
if value is None:
|
|
148
|
+
return default
|
|
149
|
+
try:
|
|
150
|
+
return int(value)
|
|
151
|
+
except ValueError:
|
|
152
|
+
return default
|
|
153
|
+
|
|
154
|
+
def _parse_bool(self, value: Optional[str], default: bool = False) -> bool:
|
|
155
|
+
if value is None:
|
|
156
|
+
return default
|
|
157
|
+
return value.lower() in ('true', '1', 'yes')
|
|
158
|
+
|
|
159
|
+
# ------------------------------------------------------------------
|
|
160
|
+
# Parse children - dispatches both qtest: and q: tags
|
|
161
|
+
# ------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
def _parse_child(self, child: ET.Element):
|
|
164
|
+
"""Parse a child element that can be either qtest: or q: namespace."""
|
|
165
|
+
ns = self._get_namespace(child)
|
|
166
|
+
local = self._get_local_name(child)
|
|
167
|
+
|
|
168
|
+
if ns == 'testing':
|
|
169
|
+
return self.parse_testing_element(local, child)
|
|
170
|
+
elif ns == 'quantum':
|
|
171
|
+
return self.parent._parse_statement(child)
|
|
172
|
+
return None
|
|
173
|
+
|
|
174
|
+
# ------------------------------------------------------------------
|
|
175
|
+
# Core testing nodes
|
|
176
|
+
# ------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
def _parse_testing_suite(self, element: ET.Element) -> QTestSuiteNode:
|
|
179
|
+
name = element.get('name', 'default')
|
|
180
|
+
node = QTestSuiteNode(name)
|
|
181
|
+
node.browser = self._parse_bool(element.get('browser'))
|
|
182
|
+
node.parallel = self._parse_bool(element.get('parallel'))
|
|
183
|
+
node.workers = self._parse_int(element.get('workers'), 1)
|
|
184
|
+
node.browsers = element.get('browsers')
|
|
185
|
+
node.device = element.get('device')
|
|
186
|
+
|
|
187
|
+
for child in element:
|
|
188
|
+
parsed = self._parse_child(child)
|
|
189
|
+
if parsed:
|
|
190
|
+
node.add_child(parsed)
|
|
191
|
+
return node
|
|
192
|
+
|
|
193
|
+
def _parse_testing_case(self, element: ET.Element) -> QTestCaseNode:
|
|
194
|
+
name = element.get('name', '')
|
|
195
|
+
node = QTestCaseNode(name)
|
|
196
|
+
node.browser = self._parse_bool(element.get('browser'))
|
|
197
|
+
node.auth = element.get('auth')
|
|
198
|
+
node.device = element.get('device')
|
|
199
|
+
node.viewports = element.get('viewports')
|
|
200
|
+
|
|
201
|
+
for child in element:
|
|
202
|
+
parsed = self._parse_child(child)
|
|
203
|
+
if parsed:
|
|
204
|
+
node.add_child(parsed)
|
|
205
|
+
return node
|
|
206
|
+
|
|
207
|
+
def _parse_testing_expect(self, element: ET.Element) -> ExpectNode:
|
|
208
|
+
node = ExpectNode()
|
|
209
|
+
node.selector = element.get('selector')
|
|
210
|
+
node.visible = self._parse_bool(element.get('visible')) if element.get('visible') else None
|
|
211
|
+
node.hidden = self._parse_bool(element.get('hidden')) if element.get('hidden') else None
|
|
212
|
+
node.text = element.get('text')
|
|
213
|
+
node.text_contains = element.get('text-contains')
|
|
214
|
+
node.equals = element.get('equals')
|
|
215
|
+
node.count = self._parse_int(element.get('count')) if element.get('count') else None
|
|
216
|
+
node.url = element.get('url')
|
|
217
|
+
node.url_contains = element.get('url-contains')
|
|
218
|
+
node.var = element.get('var')
|
|
219
|
+
node.value = element.get('value')
|
|
220
|
+
node.enabled = self._parse_bool(element.get('enabled')) if element.get('enabled') else None
|
|
221
|
+
node.disabled = self._parse_bool(element.get('disabled')) if element.get('disabled') else None
|
|
222
|
+
node.checked = self._parse_bool(element.get('checked')) if element.get('checked') else None
|
|
223
|
+
node.has_class = element.get('has-class')
|
|
224
|
+
node.has_attribute = element.get('has-attribute')
|
|
225
|
+
node.not_ = self._parse_bool(element.get('not'))
|
|
226
|
+
return node
|
|
227
|
+
|
|
228
|
+
def _parse_testing_mock(self, element: ET.Element) -> MockNode_Testing:
|
|
229
|
+
node = MockNode_Testing()
|
|
230
|
+
node.query = element.get('query')
|
|
231
|
+
node.service = element.get('service')
|
|
232
|
+
node.auto_generate = self._parse_bool(element.get('auto-generate'))
|
|
233
|
+
node.when = element.get('when')
|
|
234
|
+
node.return_value = (element.text or '').strip() or None
|
|
235
|
+
|
|
236
|
+
for child in element:
|
|
237
|
+
parsed = self._parse_child(child)
|
|
238
|
+
if parsed:
|
|
239
|
+
node.add_child(parsed)
|
|
240
|
+
return node
|
|
241
|
+
|
|
242
|
+
def _parse_testing_fixture(self, element: ET.Element) -> FixtureNode_Testing:
|
|
243
|
+
name = element.get('name', '')
|
|
244
|
+
node = FixtureNode_Testing(name)
|
|
245
|
+
node.file = element.get('file')
|
|
246
|
+
node.scope = element.get('scope', 'function')
|
|
247
|
+
node.data = (element.text or '').strip() or None
|
|
248
|
+
return node
|
|
249
|
+
|
|
250
|
+
def _parse_testing_setup(self, element: ET.Element) -> SetupNode_Testing:
|
|
251
|
+
node = SetupNode_Testing()
|
|
252
|
+
for child in element:
|
|
253
|
+
parsed = self._parse_child(child)
|
|
254
|
+
if parsed:
|
|
255
|
+
node.add_child(parsed)
|
|
256
|
+
return node
|
|
257
|
+
|
|
258
|
+
def _parse_testing_teardown(self, element: ET.Element) -> TeardownNode_Testing:
|
|
259
|
+
node = TeardownNode_Testing()
|
|
260
|
+
for child in element:
|
|
261
|
+
parsed = self._parse_child(child)
|
|
262
|
+
if parsed:
|
|
263
|
+
node.add_child(parsed)
|
|
264
|
+
return node
|
|
265
|
+
|
|
266
|
+
def _parse_testing_before_each(self, element: ET.Element) -> BeforeEachNode:
|
|
267
|
+
node = BeforeEachNode()
|
|
268
|
+
for child in element:
|
|
269
|
+
parsed = self._parse_child(child)
|
|
270
|
+
if parsed:
|
|
271
|
+
node.add_child(parsed)
|
|
272
|
+
return node
|
|
273
|
+
|
|
274
|
+
def _parse_testing_after_each(self, element: ET.Element) -> AfterEachNode:
|
|
275
|
+
node = AfterEachNode()
|
|
276
|
+
for child in element:
|
|
277
|
+
parsed = self._parse_child(child)
|
|
278
|
+
if parsed:
|
|
279
|
+
node.add_child(parsed)
|
|
280
|
+
return node
|
|
281
|
+
|
|
282
|
+
def _parse_testing_generate(self, element: ET.Element) -> GenerateNode:
|
|
283
|
+
node = GenerateNode()
|
|
284
|
+
node.component = element.get('component')
|
|
285
|
+
node.coverage = element.get('coverage')
|
|
286
|
+
node.include = element.get('include')
|
|
287
|
+
return node
|
|
288
|
+
|
|
289
|
+
def _parse_testing_scenario(self, element: ET.Element) -> ScenarioNode:
|
|
290
|
+
name = element.get('name', '')
|
|
291
|
+
node = ScenarioNode(name)
|
|
292
|
+
for child in element:
|
|
293
|
+
parsed = self._parse_child(child)
|
|
294
|
+
if parsed:
|
|
295
|
+
node.add_child(parsed)
|
|
296
|
+
return node
|
|
297
|
+
|
|
298
|
+
def _parse_testing_given(self, element: ET.Element) -> GivenNode:
|
|
299
|
+
text = (element.text or '').strip()
|
|
300
|
+
node = GivenNode(text)
|
|
301
|
+
for child in element:
|
|
302
|
+
parsed = self._parse_child(child)
|
|
303
|
+
if parsed:
|
|
304
|
+
node.add_child(parsed)
|
|
305
|
+
return node
|
|
306
|
+
|
|
307
|
+
def _parse_testing_when(self, element: ET.Element) -> WhenNode:
|
|
308
|
+
text = (element.text or '').strip()
|
|
309
|
+
node = WhenNode(text)
|
|
310
|
+
for child in element:
|
|
311
|
+
parsed = self._parse_child(child)
|
|
312
|
+
if parsed:
|
|
313
|
+
node.add_child(parsed)
|
|
314
|
+
return node
|
|
315
|
+
|
|
316
|
+
def _parse_testing_then(self, element: ET.Element) -> ThenNode:
|
|
317
|
+
text = (element.text or '').strip()
|
|
318
|
+
node = ThenNode(text)
|
|
319
|
+
for child in element:
|
|
320
|
+
parsed = self._parse_child(child)
|
|
321
|
+
if parsed:
|
|
322
|
+
node.add_child(parsed)
|
|
323
|
+
return node
|
|
324
|
+
|
|
325
|
+
# ------------------------------------------------------------------
|
|
326
|
+
# Browser testing nodes
|
|
327
|
+
# ------------------------------------------------------------------
|
|
328
|
+
|
|
329
|
+
def _parse_testing_browser_config(self, element: ET.Element) -> BrowserConfigNode:
|
|
330
|
+
node = BrowserConfigNode()
|
|
331
|
+
node.engine = element.get('engine', 'chromium')
|
|
332
|
+
node.headless = self._parse_bool(element.get('headless'), True)
|
|
333
|
+
node.viewport_width = self._parse_int(element.get('viewport-width'), 1280)
|
|
334
|
+
node.viewport_height = self._parse_int(element.get('viewport-height'), 720)
|
|
335
|
+
node.locale = element.get('locale')
|
|
336
|
+
node.timezone = element.get('timezone')
|
|
337
|
+
node.base_url = element.get('base-url')
|
|
338
|
+
node.timeout = self._parse_int(element.get('timeout'), 30000)
|
|
339
|
+
node.video = self._parse_bool(element.get('video'))
|
|
340
|
+
node.trace = self._parse_bool(element.get('trace'))
|
|
341
|
+
node.screenshot = element.get('screenshot')
|
|
342
|
+
return node
|
|
343
|
+
|
|
344
|
+
def _parse_testing_navigate(self, element: ET.Element) -> NavigateNode:
|
|
345
|
+
node = NavigateNode()
|
|
346
|
+
node.to = element.get('to')
|
|
347
|
+
node.back = self._parse_bool(element.get('back'))
|
|
348
|
+
node.forward = self._parse_bool(element.get('forward'))
|
|
349
|
+
node.wait = element.get('wait')
|
|
350
|
+
return node
|
|
351
|
+
|
|
352
|
+
def _parse_testing_click(self, element: ET.Element) -> ClickNode:
|
|
353
|
+
node = ClickNode()
|
|
354
|
+
node.selector = element.get('selector')
|
|
355
|
+
node.text = element.get('text')
|
|
356
|
+
node.role = element.get('role')
|
|
357
|
+
node.test_id = element.get('test-id')
|
|
358
|
+
node.xpath = element.get('xpath')
|
|
359
|
+
return node
|
|
360
|
+
|
|
361
|
+
def _parse_testing_dblclick(self, element: ET.Element) -> DblClickNode:
|
|
362
|
+
node = DblClickNode()
|
|
363
|
+
node.selector = element.get('selector')
|
|
364
|
+
node.text = element.get('text')
|
|
365
|
+
node.role = element.get('role')
|
|
366
|
+
node.test_id = element.get('test-id')
|
|
367
|
+
return node
|
|
368
|
+
|
|
369
|
+
def _parse_testing_right_click(self, element: ET.Element) -> RightClickNode:
|
|
370
|
+
node = RightClickNode()
|
|
371
|
+
node.selector = element.get('selector')
|
|
372
|
+
node.text = element.get('text')
|
|
373
|
+
node.role = element.get('role')
|
|
374
|
+
node.test_id = element.get('test-id')
|
|
375
|
+
return node
|
|
376
|
+
|
|
377
|
+
def _parse_testing_hover(self, element: ET.Element) -> HoverNode:
|
|
378
|
+
node = HoverNode()
|
|
379
|
+
node.selector = element.get('selector')
|
|
380
|
+
node.text = element.get('text')
|
|
381
|
+
node.role = element.get('role')
|
|
382
|
+
node.test_id = element.get('test-id')
|
|
383
|
+
return node
|
|
384
|
+
|
|
385
|
+
def _parse_testing_fill(self, element: ET.Element) -> FillNode:
|
|
386
|
+
node = FillNode()
|
|
387
|
+
node.selector = element.get('selector')
|
|
388
|
+
node.value = element.get('value', '')
|
|
389
|
+
node.clear = self._parse_bool(element.get('clear'), True)
|
|
390
|
+
return node
|
|
391
|
+
|
|
392
|
+
def _parse_testing_type(self, element: ET.Element) -> TypeNode_Testing:
|
|
393
|
+
node = TypeNode_Testing()
|
|
394
|
+
node.selector = element.get('selector')
|
|
395
|
+
node.text = element.get('text', '')
|
|
396
|
+
node.delay = self._parse_int(element.get('delay'))
|
|
397
|
+
return node
|
|
398
|
+
|
|
399
|
+
def _parse_testing_select(self, element: ET.Element) -> SelectNode_Testing:
|
|
400
|
+
node = SelectNode_Testing()
|
|
401
|
+
node.selector = element.get('selector')
|
|
402
|
+
node.value = element.get('value')
|
|
403
|
+
node.values = element.get('values')
|
|
404
|
+
return node
|
|
405
|
+
|
|
406
|
+
def _parse_testing_check(self, element: ET.Element) -> CheckNode:
|
|
407
|
+
node = CheckNode()
|
|
408
|
+
node.selector = element.get('selector')
|
|
409
|
+
return node
|
|
410
|
+
|
|
411
|
+
def _parse_testing_uncheck(self, element: ET.Element) -> UncheckNode:
|
|
412
|
+
node = UncheckNode()
|
|
413
|
+
node.selector = element.get('selector')
|
|
414
|
+
return node
|
|
415
|
+
|
|
416
|
+
def _parse_testing_keyboard(self, element: ET.Element) -> KeyboardNode:
|
|
417
|
+
node = KeyboardNode()
|
|
418
|
+
node.press = element.get('press')
|
|
419
|
+
node.type_text = element.get('type')
|
|
420
|
+
node.delay = self._parse_int(element.get('delay'))
|
|
421
|
+
return node
|
|
422
|
+
|
|
423
|
+
def _parse_testing_drag(self, element: ET.Element) -> DragNode:
|
|
424
|
+
node = DragNode()
|
|
425
|
+
node.from_sel = element.get('from')
|
|
426
|
+
node.to_sel = element.get('to')
|
|
427
|
+
return node
|
|
428
|
+
|
|
429
|
+
def _parse_testing_scroll(self, element: ET.Element) -> ScrollNode_Testing:
|
|
430
|
+
node = ScrollNode_Testing()
|
|
431
|
+
node.selector = element.get('selector')
|
|
432
|
+
node.direction = element.get('direction', 'down')
|
|
433
|
+
node.amount = self._parse_int(element.get('amount')) if element.get('amount') else None
|
|
434
|
+
node.to_element = element.get('to-element')
|
|
435
|
+
node.to_top = self._parse_bool(element.get('to-top'))
|
|
436
|
+
return node
|
|
437
|
+
|
|
438
|
+
def _parse_testing_upload(self, element: ET.Element) -> UploadNode:
|
|
439
|
+
node = UploadNode()
|
|
440
|
+
node.selector = element.get('selector')
|
|
441
|
+
node.file = element.get('file')
|
|
442
|
+
node.files = element.get('files')
|
|
443
|
+
return node
|
|
444
|
+
|
|
445
|
+
def _parse_testing_download(self, element: ET.Element) -> DownloadNode:
|
|
446
|
+
node = DownloadNode()
|
|
447
|
+
node.trigger_click = element.get('trigger-click')
|
|
448
|
+
node.save_as = element.get('save-as')
|
|
449
|
+
return node
|
|
450
|
+
|
|
451
|
+
def _parse_testing_wait_for(self, element: ET.Element) -> WaitForNode:
|
|
452
|
+
node = WaitForNode()
|
|
453
|
+
node.selector = element.get('selector')
|
|
454
|
+
node.state = element.get('state')
|
|
455
|
+
node.url = element.get('url')
|
|
456
|
+
node.request = element.get('request')
|
|
457
|
+
node.response = element.get('response')
|
|
458
|
+
node.condition = element.get('condition')
|
|
459
|
+
node.timeout = self._parse_int(element.get('timeout')) if element.get('timeout') else None
|
|
460
|
+
node.has_text = element.get('has-text')
|
|
461
|
+
return node
|
|
462
|
+
|
|
463
|
+
def _parse_testing_screenshot(self, element: ET.Element) -> ScreenshotNode:
|
|
464
|
+
node = ScreenshotNode()
|
|
465
|
+
node.name = element.get('name')
|
|
466
|
+
node.selector = element.get('selector')
|
|
467
|
+
node.full_page = self._parse_bool(element.get('full-page'))
|
|
468
|
+
return node
|
|
469
|
+
|
|
470
|
+
def _parse_testing_intercept(self, element: ET.Element) -> InterceptNode:
|
|
471
|
+
node = InterceptNode()
|
|
472
|
+
node.method = element.get('method')
|
|
473
|
+
node.url = element.get('url')
|
|
474
|
+
node.delay = self._parse_int(element.get('delay')) if element.get('delay') else None
|
|
475
|
+
node.passthrough = self._parse_bool(element.get('passthrough'))
|
|
476
|
+
node.spy = self._parse_bool(element.get('spy'))
|
|
477
|
+
|
|
478
|
+
for child in element:
|
|
479
|
+
parsed = self._parse_child(child)
|
|
480
|
+
if parsed:
|
|
481
|
+
node.add_child(parsed)
|
|
482
|
+
return node
|
|
483
|
+
|
|
484
|
+
def _parse_testing_respond(self, element: ET.Element) -> InterceptRespondNode:
|
|
485
|
+
node = InterceptRespondNode()
|
|
486
|
+
node.status = self._parse_int(element.get('status'), 200)
|
|
487
|
+
node.content_type = element.get('content-type', 'application/json')
|
|
488
|
+
node.file = element.get('file')
|
|
489
|
+
node.body = (element.text or '').strip() or None
|
|
490
|
+
return node
|
|
491
|
+
|
|
492
|
+
def _parse_testing_frame(self, element: ET.Element) -> FrameNode_Testing:
|
|
493
|
+
node = FrameNode_Testing()
|
|
494
|
+
node.selector = element.get('selector')
|
|
495
|
+
for child in element:
|
|
496
|
+
parsed = self._parse_child(child)
|
|
497
|
+
if parsed:
|
|
498
|
+
node.add_child(parsed)
|
|
499
|
+
return node
|
|
500
|
+
|
|
501
|
+
def _parse_testing_popup(self, element: ET.Element) -> PopupNode:
|
|
502
|
+
node = PopupNode()
|
|
503
|
+
for child in element:
|
|
504
|
+
parsed = self._parse_child(child)
|
|
505
|
+
if parsed:
|
|
506
|
+
node.add_child(parsed)
|
|
507
|
+
return node
|
|
508
|
+
|
|
509
|
+
def _parse_testing_auth(self, element: ET.Element) -> AuthNode:
|
|
510
|
+
node = AuthNode()
|
|
511
|
+
node.name = element.get('name')
|
|
512
|
+
node.reuse = self._parse_bool(element.get('reuse'), True)
|
|
513
|
+
for child in element:
|
|
514
|
+
parsed = self._parse_child(child)
|
|
515
|
+
if parsed:
|
|
516
|
+
node.add_child(parsed)
|
|
517
|
+
return node
|
|
518
|
+
|
|
519
|
+
def _parse_testing_save_state(self, element: ET.Element) -> SaveStateNode:
|
|
520
|
+
node = SaveStateNode()
|
|
521
|
+
node.file = element.get('file')
|
|
522
|
+
return node
|
|
523
|
+
|
|
524
|
+
def _parse_testing_pause(self, element: ET.Element) -> PauseNode:
|
|
525
|
+
return PauseNode()
|
|
526
|
+
|
|
527
|
+
# ------------------------------------------------------------------
|
|
528
|
+
# Advanced testing nodes
|
|
529
|
+
# ------------------------------------------------------------------
|
|
530
|
+
|
|
531
|
+
def _parse_testing_snapshot(self, element: ET.Element) -> SnapshotNode:
|
|
532
|
+
node = SnapshotNode()
|
|
533
|
+
node.name = element.get('name')
|
|
534
|
+
node.threshold = self._parse_float(element.get('threshold'), 0.1)
|
|
535
|
+
node.ai_ignore = element.get('ai-ignore')
|
|
536
|
+
node.viewports = element.get('viewports')
|
|
537
|
+
node.mask_selectors = element.get('mask-selectors')
|
|
538
|
+
return node
|
|
539
|
+
|
|
540
|
+
def _parse_testing_fuzz(self, element: ET.Element) -> FuzzNode:
|
|
541
|
+
node = FuzzNode()
|
|
542
|
+
node.target = element.get('target')
|
|
543
|
+
node.iterations = self._parse_int(element.get('iterations'), 100)
|
|
544
|
+
node.generators = element.get('generators')
|
|
545
|
+
for child in element:
|
|
546
|
+
parsed = self._parse_child(child)
|
|
547
|
+
if parsed:
|
|
548
|
+
node.add_child(parsed)
|
|
549
|
+
return node
|
|
550
|
+
|
|
551
|
+
def _parse_testing_property(self, element: ET.Element) -> PropertyNode:
|
|
552
|
+
text = (element.text or '').strip()
|
|
553
|
+
return PropertyNode(text)
|
|
554
|
+
|
|
555
|
+
def _parse_testing_field(self, element: ET.Element) -> FuzzFieldNode:
|
|
556
|
+
node = FuzzFieldNode()
|
|
557
|
+
node.name = element.get('name')
|
|
558
|
+
node.generator = element.get('generator')
|
|
559
|
+
return node
|
|
560
|
+
|
|
561
|
+
def _parse_testing_perf(self, element: ET.Element) -> PerfNode:
|
|
562
|
+
node = PerfNode()
|
|
563
|
+
node.baseline = element.get('baseline')
|
|
564
|
+
node.regression_threshold = self._parse_float(element.get('regression-threshold'), 0.1)
|
|
565
|
+
for child in element:
|
|
566
|
+
parsed = self._parse_child(child)
|
|
567
|
+
if parsed:
|
|
568
|
+
node.add_child(parsed)
|
|
569
|
+
return node
|
|
570
|
+
|
|
571
|
+
def _parse_testing_metric(self, element: ET.Element) -> PerfMetricNode:
|
|
572
|
+
node = PerfMetricNode()
|
|
573
|
+
node.name = element.get('name')
|
|
574
|
+
node.max_value = self._parse_float(element.get('max-value')) if element.get('max-value') else None
|
|
575
|
+
return node
|
|
576
|
+
|
|
577
|
+
def _parse_testing_a11y(self, element: ET.Element) -> A11yNode:
|
|
578
|
+
node = A11yNode()
|
|
579
|
+
node.standard = element.get('standard', 'wcag2aa')
|
|
580
|
+
node.check = element.get('check')
|
|
581
|
+
node.ignore = element.get('ignore')
|
|
582
|
+
return node
|
|
583
|
+
|
|
584
|
+
def _parse_testing_chaos(self, element: ET.Element) -> ChaosNode:
|
|
585
|
+
node = ChaosNode()
|
|
586
|
+
for child in element:
|
|
587
|
+
parsed = self._parse_child(child)
|
|
588
|
+
if parsed:
|
|
589
|
+
node.add_child(parsed)
|
|
590
|
+
return node
|
|
591
|
+
|
|
592
|
+
def _parse_testing_fail(self, element: ET.Element) -> ChaosFailNode:
|
|
593
|
+
node = ChaosFailNode()
|
|
594
|
+
node.service = element.get('service')
|
|
595
|
+
node.probability = self._parse_float(element.get('probability'), 1.0)
|
|
596
|
+
node.delay = self._parse_int(element.get('delay')) if element.get('delay') else None
|
|
597
|
+
return node
|
|
598
|
+
|
|
599
|
+
def _parse_testing_geolocation(self, element: ET.Element) -> GeolocationNode:
|
|
600
|
+
node = GeolocationNode()
|
|
601
|
+
node.latitude = self._parse_float(element.get('latitude'))
|
|
602
|
+
node.longitude = self._parse_float(element.get('longitude'))
|
|
603
|
+
node.accuracy = self._parse_float(element.get('accuracy'), 100.0)
|
|
604
|
+
return node
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Theming System for Quantum UI Engine
|
|
3
|
+
|
|
4
|
+
Provides dark/light mode support, custom color themes, and runtime theme switching.
|
|
5
|
+
Integrates with the design tokens system for consistent styling.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
# In .q files:
|
|
9
|
+
<q:application id="MyApp" type="ui" theme="dark">
|
|
10
|
+
<ui:window>...</ui:window>
|
|
11
|
+
</q:application>
|
|
12
|
+
|
|
13
|
+
# Or with ui:theme tag:
|
|
14
|
+
<q:application id="MyApp" type="ui">
|
|
15
|
+
<ui:theme preset="dark" />
|
|
16
|
+
<ui:window>...</ui:window>
|
|
17
|
+
</q:application>
|
|
18
|
+
|
|
19
|
+
# Custom theme:
|
|
20
|
+
<q:application id="MyApp" type="ui">
|
|
21
|
+
<ui:theme name="custom">
|
|
22
|
+
<ui:color name="primary" value="#ff6b6b" />
|
|
23
|
+
<ui:color name="background" value="#2d3436" />
|
|
24
|
+
</ui:theme>
|
|
25
|
+
<ui:window>...</ui:window>
|
|
26
|
+
</q:application>
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from .ast_node import UIThemeNode, UIColorNode
|
|
30
|
+
from .presets import (
|
|
31
|
+
THEME_LIGHT,
|
|
32
|
+
THEME_DARK,
|
|
33
|
+
THEME_PRESETS,
|
|
34
|
+
get_theme_css,
|
|
35
|
+
get_theme_preset,
|
|
36
|
+
get_theme_switch_js,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
'UIThemeNode',
|
|
41
|
+
'UIColorNode',
|
|
42
|
+
'THEME_LIGHT',
|
|
43
|
+
'THEME_DARK',
|
|
44
|
+
'THEME_PRESETS',
|
|
45
|
+
'get_theme_css',
|
|
46
|
+
'get_theme_preset',
|
|
47
|
+
'get_theme_switch_js',
|
|
48
|
+
]
|