simvx.core.ui.testing¶
Headless UI test harness — validate widgets without GPU or window.
Provides UITestHarness for frame simulation, input injection, and draw capture. All tests run on CPU only; no Vulkan, GLFW, or display required.
Example: from simvx.core import Button, VBoxContainer, SceneTree from simvx.core.ui.testing import UITestHarness
def test_button_click():
root = VBoxContainer()
btn = root.add_child(Button("Save"))
harness = UITestHarness(root)
clicks = []
btn.pressed.connect(lambda: clicks.append(1))
harness.click(btn)
assert len(clicks) == 1
Module Contents¶
Classes¶
One recorded draw command. |
|
Mock renderer that records draw commands for assertion. |
|
Headless UI test harness for programmatic widget testing. |
Data¶
API¶
- simvx.core.ui.testing.__all__¶
[‘UITestHarness’, ‘DrawLog’, ‘DrawCall’]
- class simvx.core.ui.testing.DrawCall¶
One recorded draw command.
- type: str¶
None
- x: float¶
0.0
- y: float¶
0.0
- w: float¶
0.0
- h: float¶
0.0
- color: tuple[float, ...]¶
()
- text: str = <Multiline-String>¶
- scale: float¶
1.0
- x2: float¶
0.0
- y2: float¶
0.0
- class simvx.core.ui.testing.DrawLog¶
Mock renderer that records draw commands for assertion.
Implements the same interface as Draw2D so widgets can render into it. All geometry is captured but not rasterized.
Example: harness.tick() assert “Save” in harness.draw_log.texts() assert harness.draw_log.rects_at(100, 50)
Initialization
- clear()¶
Discard all recorded calls.
- draw_filled_rect(x: float, y: float, w: float, h: float, color: tuple = (1, 1, 1, 1))¶
- draw_rect_colored(x: float, y: float, w: float, h: float, color: tuple = (1, 1, 1, 1))¶
- draw_line_colored(x1: float, y1: float, x2: float, y2: float, color: tuple = (1, 1, 1, 1))¶
- draw_text_colored(text: str, x: float, y: float, scale: float = 1.0, color: tuple = (1, 1, 1, 1))¶
- text_width(text: str, scale: float = 1.0) float¶
Approximate text width: 8px per character at scale 1.0.
- push_clip(x: float, y: float, w: float, h: float)¶
- pop_clip()¶
- new_layer()¶
- set_color(r=255, g=255, b=255, a=255)¶
Track current colour (no-op for recording; color is per-call).
- fill_rect(x: float, y: float, w: float, h: float, color: tuple = (1, 1, 1, 1))¶
- draw_rect(x: float, y: float, w: float, h: float, color: tuple = (1, 1, 1, 1))¶
- draw_line(x1: float, y1: float, x2: float, y2: float, color: tuple = (1, 1, 1, 1))¶
- draw_text(text: str, pos: tuple, scale: float = 1.0, color: tuple = (1, 1, 1, 1))¶
- fill_circle(cx: float, cy: float, radius: float, segments: int = 24)¶
- draw_thick_line(x1: float, y1: float, x2: float, y2: float, width: float = 2.0)¶
- draw_circle(center, radius_or_y=None, radius=None, segments=24)¶
- draw_lines(points, closed=True, color=None)¶
- fill_triangle(x1, y1, x2, y2, x3, y3)¶
- texts() list[str]¶
All rendered text strings.
- texts_containing(substring: str) list[str]¶
Text strings containing a substring.
- rects_at(x: float, y: float) list[simvx.core.ui.testing.DrawCall]¶
All fill_rect calls whose bounds contain (x, y).
- calls_of_type(call_type: str) list[simvx.core.ui.testing.DrawCall]¶
All calls of a specific type.
- has_text(text: str) bool¶
Check if exact text was rendered.
- has_text_containing(substring: str) bool¶
Check if any rendered text contains substring.
- class simvx.core.ui.testing.UITestHarness(root: simvx.core.ui.core.Control, screen_size: tuple[float, float] = (1280, 720))¶
Headless UI test harness for programmatic widget testing.
Creates a SceneTree with the given root control, provides input injection helpers, frame simulation, draw capture, and widget lookup.
Example: panel = VBoxContainer() btn = panel.add_child(Button(“OK”)) harness = UITestHarness(panel)
harness.click(btn) harness.type_text("hello") harness.press_key("enter") harness.tick() assert harness.draw_log.has_text("OK")Initialization
- property root: simvx.core.ui.core.Control¶
- teardown()¶
Tear down the scene tree, breaking reference cycles to free memory.
Idempotent — safe to call multiple times. Recursively severs every parent↔child and node↔tree back-reference so the entire node graph becomes immediately reclaimable by refcount alone (no GC needed).
- tick(dt: float = 1 / 60, count: int = 1)¶
Advance count frames: process → draw for each.
Draw log is cleared before the first frame, then accumulates across all frames in this tick call.
- process_only(dt: float = 1 / 60, count: int = 1)¶
Advance frames without drawing (faster for logic-only tests).
- click(target: simvx.core.ui.core.Control | simvx.core.math.types.Vec2 | tuple[float, float], button: int = 1)¶
Click (press + release) at widget center or screen position.
- double_click(target: simvx.core.ui.core.Control | simvx.core.math.types.Vec2 | tuple[float, float], button: int = 1)¶
Two rapid clicks at the same position.
- mouse_down(target: simvx.core.ui.core.Control | simvx.core.math.types.Vec2 | tuple[float, float], button: int = 1)¶
Press mouse button without releasing.
- mouse_up(target: simvx.core.ui.core.Control | simvx.core.math.types.Vec2 | tuple[float, float], button: int = 1)¶
Release mouse button.
- mouse_move(target: simvx.core.ui.core.Control | simvx.core.math.types.Vec2 | tuple[float, float])¶
Move mouse to position (triggers hover/mouse_over updates).
- drag(start: simvx.core.ui.core.Control | simvx.core.math.types.Vec2, end: simvx.core.ui.core.Control | simvx.core.math.types.Vec2, steps: int = 5, button: int = 1)¶
Simulate mouse drag with intermediate moves.
- type_text(text: str)¶
Send character events for each char in text to the focused widget.
- press_key(key: str, release: bool = True)¶
Send key press (and optionally release) event.
Supports modifier combos: “ctrl+s”, “shift+enter”, “escape”. For modifier combos, temporarily sets Input._keys so SceneTree builds the correct combo_key string.
- scroll(target: simvx.core.ui.core.Control | simvx.core.math.types.Vec2 | tuple[float, float], direction: str = 'down', amount: int = 1)¶
Send scroll events. direction is ‘up’ or ‘down’.
- set_focus(control: simvx.core.ui.core.Control)¶
Directly focus a control.
- find(path: str) simvx.core.ui.core.Control | None¶
Find widget by slash-separated name path from root.
Example: harness.find(“Layout/TabContainer/Editor”)
- find_all(widget_type: type) list[simvx.core.ui.core.Control]¶
Find all widgets of a given type in the tree.
- find_by_text(text: str) simvx.core.ui.core.Control | None¶
Find first Label or Button whose .text contains the given string.
- find_by_name(name: str) simvx.core.ui.core.Control | None¶
Find first widget with the given name (depth-first).
- assert_visible(widget: simvx.core.ui.core.Control, msg: str = '')¶
Assert widget is visible.
Assert widget is not visible.
- assert_focused(widget: simvx.core.ui.core.Control, msg: str = '')¶
Assert widget has focus.
- assert_text(widget: simvx.core.ui.core.Control, expected: str, msg: str = '')¶
Assert widget.text matches expected.
- assert_text_contains(widget: simvx.core.ui.core.Control, substring: str, msg: str = '')¶
Assert widget.text contains substring.
- assert_drawn_text(text: str, msg: str = '')¶
Assert text appears in the draw log.
- assert_drawn_text_containing(substring: str, msg: str = '')¶
Assert any drawn text contains substring.
- assert_signal_emitted(signal, msg: str = '') list¶
Connect to a signal and return a list that collects emissions.
Call this BEFORE the action that should emit. Check the list after.
Example: emissions = harness.assert_signal_emitted(btn.pressed) harness.click(btn) assert len(emissions) == 1